1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026502750285029503050315032503350345035503650375038503950405041504250435044504550465047504850495050505150525053505450555056505750585059506050615062506350645065506650675068506950705071507250735074507550765077507850795080508150825083508450855086508750885089509050915092509350945095509650975098509951005101510251035104510551065107510851095110511151125113511451155116511751185119512051215122512351245125512651275128512951305131513251335134513551365137513851395140514151425143514451455146514751485149515051515152515351545155515651575158515951605161516251635164516551665167516851695170517151725173517451755176517751785179518051815182518351845185518651875188518951905191519251935194519551965197519851995200520152025203520452055206520752085209521052115212521352145215521652175218521952205221522252235224522552265227522852295230523152325233523452355236523752385239524052415242524352445245524652475248524952505251525252535254525552565257525852595260526152625263526452655266526752685269527052715272527352745275527652775278527952805281528252835284528552865287528852895290529152925293529452955296529752985299530053015302530353045305530653075308530953105311531253135314531553165317531853195320532153225323532453255326532753285329533053315332533353345335533653375338533953405341534253435344534553465347534853495350535153525353535453555356535753585359536053615362536353645365536653675368536953705371537253735374537553765377537853795380538153825383538453855386538753885389539053915392539353945395539653975398539954005401540254035404540554065407540854095410541154125413541454155416541754185419542054215422542354245425542654275428542954305431543254335434543554365437543854395440544154425443544454455446544754485449545054515452545354545455545654575458545954605461546254635464546554665467546854695470547154725473547454755476547754785479548054815482548354845485548654875488548954905491549254935494549554965497549854995500550155025503550455055506550755085509551055115512551355145515551655175518551955205521552255235524552555265527552855295530553155325533553455355536553755385539554055415542 |
- package htlcswitch
- import (
- "context"
- "crypto/rand"
- "crypto/sha256"
- "fmt"
- "io"
- mrand "math/rand"
- "reflect"
- "testing"
- "time"
- "github.com/btcsuite/btcd/btcutil"
- "github.com/davecgh/go-spew/spew"
- "github.com/go-errors/errors"
- "github.com/lightningnetwork/lnd/chainntnfs"
- "github.com/lightningnetwork/lnd/channeldb"
- "github.com/lightningnetwork/lnd/channeldb/models"
- "github.com/lightningnetwork/lnd/contractcourt"
- "github.com/lightningnetwork/lnd/htlcswitch/hodl"
- "github.com/lightningnetwork/lnd/htlcswitch/hop"
- "github.com/lightningnetwork/lnd/lntest/mock"
- "github.com/lightningnetwork/lnd/lntypes"
- "github.com/lightningnetwork/lnd/lnwire"
- "github.com/lightningnetwork/lnd/ticker"
- "github.com/stretchr/testify/require"
- )
- var zeroCircuit = models.CircuitKey{}
- var emptyScid = lnwire.ShortChannelID{}
- func genPreimage() ([32]byte, error) {
- var preimage [32]byte
- if _, err := io.ReadFull(rand.Reader, preimage[:]); err != nil {
- return preimage, err
- }
- return preimage, nil
- }
- // TestSwitchAddDuplicateLink tests that the switch will reject duplicate links
- // for live links. It also tests that we can successfully add a link after
- // having removed it.
- func TestSwitchAddDuplicateLink(t *testing.T) {
- t.Parallel()
- alicePeer, err := newMockServer(
- t, "alice", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err, "unable to create alice server")
- s, err := initSwitchWithTempDB(t, testStartingHeight)
- require.NoError(t, err, "unable to init switch")
- if err := s.Start(); err != nil {
- t.Fatalf("unable to start switch: %v", err)
- }
- defer s.Stop()
- chanID1, aliceScid := genID()
- aliceChannelLink := newMockChannelLink(
- s, chanID1, aliceScid, emptyScid, alicePeer, false, false,
- false, false,
- )
- if err := s.AddLink(aliceChannelLink); err != nil {
- t.Fatalf("unable to add alice link: %v", err)
- }
- // Alice should have a live link, adding again should fail.
- if err := s.AddLink(aliceChannelLink); err == nil {
- t.Fatalf("adding duplicate link should have failed")
- }
- // Remove the live link to ensure the indexes are cleared.
- s.RemoveLink(chanID1)
- // Alice has no links, adding should succeed.
- if err := s.AddLink(aliceChannelLink); err != nil {
- t.Fatalf("unable to add alice link: %v", err)
- }
- }
- // TestSwitchHasActiveLink tests the behavior of HasActiveLink, and asserts that
- // it only returns true if a link's short channel id has confirmed (meaning the
- // channel is no longer pending) and it's EligibleToForward method returns true,
- // i.e. it has received ChannelReady from the remote peer.
- func TestSwitchHasActiveLink(t *testing.T) {
- t.Parallel()
- alicePeer, err := newMockServer(
- t, "alice", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err, "unable to create alice server")
- s, err := initSwitchWithTempDB(t, testStartingHeight)
- require.NoError(t, err, "unable to init switch")
- if err := s.Start(); err != nil {
- t.Fatalf("unable to start switch: %v", err)
- }
- defer s.Stop()
- chanID1, aliceScid := genID()
- aliceChannelLink := newMockChannelLink(
- s, chanID1, aliceScid, emptyScid, alicePeer, false, false,
- false, false,
- )
- if err := s.AddLink(aliceChannelLink); err != nil {
- t.Fatalf("unable to add alice link: %v", err)
- }
- // The link has been added, but it's still pending. HasActiveLink should
- // return false since the link has not been added to the linkIndex
- // containing live links.
- if s.HasActiveLink(chanID1) {
- t.Fatalf("link should not be active yet, still pending")
- }
- // Finally, simulate the link receiving channel_ready by setting its
- // eligibility to true.
- aliceChannelLink.eligible = true
- // The link should now be reported as active, since EligibleToForward
- // returns true and the link is in the linkIndex.
- if !s.HasActiveLink(chanID1) {
- t.Fatalf("link should not be active now")
- }
- }
- // TestSwitchSendPending checks the inability of htlc switch to forward adds
- // over pending links.
- func TestSwitchSendPending(t *testing.T) {
- t.Parallel()
- alicePeer, err := newMockServer(
- t, "alice", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err, "unable to create alice server")
- bobPeer, err := newMockServer(
- t, "bob", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err, "unable to create bob server")
- s, err := initSwitchWithTempDB(t, testStartingHeight)
- require.NoError(t, err, "unable to init switch")
- if err := s.Start(); err != nil {
- t.Fatalf("unable to start switch: %v", err)
- }
- defer s.Stop()
- chanID1, chanID2, aliceChanID, bobChanID := genIDs()
- pendingChanID := lnwire.ShortChannelID{}
- aliceChannelLink := newMockChannelLink(
- s, chanID1, pendingChanID, emptyScid, alicePeer, false, false,
- false, false,
- )
- if err := s.AddLink(aliceChannelLink); err != nil {
- t.Fatalf("unable to add alice link: %v", err)
- }
- bobChannelLink := newMockChannelLink(
- s, chanID2, bobChanID, emptyScid, bobPeer, true, false, false,
- false,
- )
- if err := s.AddLink(bobChannelLink); err != nil {
- t.Fatalf("unable to add bob link: %v", err)
- }
- // Create request which should is being forwarded from Bob channel
- // link to Alice channel link.
- preimage, err := genPreimage()
- require.NoError(t, err, "unable to generate preimage")
- rhash := sha256.Sum256(preimage[:])
- packet := &htlcPacket{
- incomingChanID: bobChanID,
- incomingHTLCID: 0,
- outgoingChanID: aliceChanID,
- obfuscator: NewMockObfuscator(),
- htlc: &lnwire.UpdateAddHTLC{
- PaymentHash: rhash,
- Amount: 1,
- },
- }
- // Send the ADD packet, this should not be forwarded out to the link
- // since there are no eligible links.
- if err = s.ForwardPackets(nil, packet); err != nil {
- t.Fatal(err)
- }
- select {
- case p := <-bobChannelLink.packets:
- if p.linkFailure != nil {
- err = p.linkFailure
- }
- case <-time.After(time.Second):
- t.Fatal("no timely reply from switch")
- }
- linkErr, ok := err.(*LinkError)
- if !ok {
- t.Fatalf("expected link error, got: %T", err)
- }
- if linkErr.WireMessage().Code() != lnwire.CodeUnknownNextPeer {
- t.Fatalf("expected fail unknown next peer, got: %T",
- linkErr.WireMessage().Code())
- }
- // No message should be sent, since the packet was failed.
- select {
- case <-aliceChannelLink.packets:
- t.Fatal("expected not to receive message")
- case <-time.After(time.Second):
- }
- // Since the packet should have been failed, there should be no active
- // circuits.
- if s.circuits.NumOpen() != 0 {
- t.Fatal("wrong amount of circuits")
- }
- }
- // TestSwitchForwardMapping checks that the Switch properly consults its maps
- // when forwarding packets.
- func TestSwitchForwardMapping(t *testing.T) {
- tests := []struct {
- name string
- // If this is true, then Alice's channel will be private.
- alicePrivate bool
- // If this is true, then Alice's channel will be a zero-conf
- // channel.
- zeroConf bool
- // If this is true, then Alice's channel will be an
- // option-scid-alias feature-bit, non-zero-conf channel.
- optionScid bool
- // If this is true, then an alias will be used for forwarding.
- useAlias bool
- // This is Alice's channel alias. This may not be set if this
- // is not an option_scid_alias channel (feature bit).
- aliceAlias lnwire.ShortChannelID
- // This is Alice's confirmed SCID. This may not be set if this
- // is a zero-conf channel before confirmation.
- aliceReal lnwire.ShortChannelID
- // If this is set, we expect Bob forwarding to Alice to fail.
- expectErr bool
- }{
- {
- name: "private unconfirmed zero-conf",
- alicePrivate: true,
- zeroConf: true,
- useAlias: true,
- aliceAlias: lnwire.ShortChannelID{
- BlockHeight: 16_000_002,
- TxIndex: 2,
- TxPosition: 2,
- },
- aliceReal: lnwire.ShortChannelID{},
- expectErr: false,
- },
- {
- name: "private confirmed zero-conf",
- alicePrivate: true,
- zeroConf: true,
- useAlias: true,
- aliceAlias: lnwire.ShortChannelID{
- BlockHeight: 16_000_003,
- TxIndex: 3,
- TxPosition: 3,
- },
- aliceReal: lnwire.ShortChannelID{
- BlockHeight: 300000,
- TxIndex: 3,
- TxPosition: 3,
- },
- expectErr: false,
- },
- {
- name: "private confirmed zero-conf failure",
- alicePrivate: true,
- zeroConf: true,
- useAlias: false,
- aliceAlias: lnwire.ShortChannelID{
- BlockHeight: 16_000_004,
- TxIndex: 4,
- TxPosition: 4,
- },
- aliceReal: lnwire.ShortChannelID{
- BlockHeight: 300002,
- TxIndex: 4,
- TxPosition: 4,
- },
- expectErr: true,
- },
- {
- name: "public unconfirmed zero-conf",
- alicePrivate: false,
- zeroConf: true,
- useAlias: true,
- aliceAlias: lnwire.ShortChannelID{
- BlockHeight: 16_000_005,
- TxIndex: 5,
- TxPosition: 5,
- },
- aliceReal: lnwire.ShortChannelID{},
- expectErr: false,
- },
- {
- name: "public confirmed zero-conf w/ alias",
- alicePrivate: false,
- zeroConf: true,
- useAlias: true,
- aliceAlias: lnwire.ShortChannelID{
- BlockHeight: 16_000_006,
- TxIndex: 6,
- TxPosition: 6,
- },
- aliceReal: lnwire.ShortChannelID{
- BlockHeight: 500000,
- TxIndex: 6,
- TxPosition: 6,
- },
- expectErr: false,
- },
- {
- name: "public confirmed zero-conf w/ real",
- alicePrivate: false,
- zeroConf: true,
- useAlias: false,
- aliceAlias: lnwire.ShortChannelID{
- BlockHeight: 16_000_007,
- TxIndex: 7,
- TxPosition: 7,
- },
- aliceReal: lnwire.ShortChannelID{
- BlockHeight: 502000,
- TxIndex: 7,
- TxPosition: 7,
- },
- expectErr: false,
- },
- {
- name: "private non-option channel",
- alicePrivate: true,
- aliceAlias: lnwire.ShortChannelID{},
- aliceReal: lnwire.ShortChannelID{
- BlockHeight: 505000,
- TxIndex: 8,
- TxPosition: 8,
- },
- },
- {
- name: "private option channel w/ alias",
- alicePrivate: true,
- optionScid: true,
- useAlias: true,
- aliceAlias: lnwire.ShortChannelID{
- BlockHeight: 16_000_015,
- TxIndex: 9,
- TxPosition: 9,
- },
- aliceReal: lnwire.ShortChannelID{
- BlockHeight: 506000,
- TxIndex: 10,
- TxPosition: 10,
- },
- expectErr: false,
- },
- {
- name: "private option channel failure",
- alicePrivate: true,
- optionScid: true,
- useAlias: false,
- aliceAlias: lnwire.ShortChannelID{
- BlockHeight: 16_000_016,
- TxIndex: 16,
- TxPosition: 16,
- },
- aliceReal: lnwire.ShortChannelID{
- BlockHeight: 507000,
- TxIndex: 17,
- TxPosition: 17,
- },
- expectErr: true,
- },
- {
- name: "public non-option channel",
- alicePrivate: false,
- useAlias: false,
- aliceAlias: lnwire.ShortChannelID{},
- aliceReal: lnwire.ShortChannelID{
- BlockHeight: 508000,
- TxIndex: 17,
- TxPosition: 17,
- },
- expectErr: false,
- },
- {
- name: "public option channel w/ alias",
- alicePrivate: false,
- optionScid: true,
- useAlias: true,
- aliceAlias: lnwire.ShortChannelID{
- BlockHeight: 16_000_018,
- TxIndex: 18,
- TxPosition: 18,
- },
- aliceReal: lnwire.ShortChannelID{
- BlockHeight: 509000,
- TxIndex: 19,
- TxPosition: 19,
- },
- expectErr: false,
- },
- {
- name: "public option channel w/ real",
- alicePrivate: false,
- optionScid: true,
- useAlias: false,
- aliceAlias: lnwire.ShortChannelID{
- BlockHeight: 16_000_019,
- TxIndex: 19,
- TxPosition: 19,
- },
- aliceReal: lnwire.ShortChannelID{
- BlockHeight: 510000,
- TxIndex: 20,
- TxPosition: 20,
- },
- expectErr: false,
- },
- }
- for _, test := range tests {
- test := test
- t.Run(test.name, func(t *testing.T) {
- t.Parallel()
- testSwitchForwardMapping(
- t, test.alicePrivate, test.zeroConf,
- test.useAlias, test.optionScid,
- test.aliceAlias, test.aliceReal,
- test.expectErr,
- )
- })
- }
- }
- func testSwitchForwardMapping(t *testing.T, alicePrivate, aliceZeroConf,
- useAlias, optionScid bool, aliceAlias, aliceReal lnwire.ShortChannelID,
- expectErr bool) {
- alicePeer, err := newMockServer(
- t, "alice", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err)
- bobPeer, err := newMockServer(
- t, "bob", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err)
- s, err := initSwitchWithTempDB(t, testStartingHeight)
- require.NoError(t, err)
- err = s.Start()
- require.NoError(t, err)
- defer func() { _ = s.Stop() }()
- // Create the lnwire.ChannelIDs that we'll use.
- chanID1, chanID2, _, _ := genIDs()
- var aliceChannelLink *mockChannelLink
- if aliceZeroConf {
- aliceChannelLink = newMockChannelLink(
- s, chanID1, aliceAlias, aliceReal, alicePeer, true,
- alicePrivate, true, false,
- )
- } else {
- aliceChannelLink = newMockChannelLink(
- s, chanID1, aliceReal, emptyScid, alicePeer, true,
- alicePrivate, false, optionScid,
- )
- if optionScid {
- aliceChannelLink.addAlias(aliceAlias)
- }
- }
- err = s.AddLink(aliceChannelLink)
- require.NoError(t, err)
- // Bob will just have a non-option_scid_alias channel so no mapping is
- // necessary.
- bobScid := lnwire.ShortChannelID{
- BlockHeight: 501000,
- TxIndex: 200,
- TxPosition: 2,
- }
- bobChannelLink := newMockChannelLink(
- s, chanID2, bobScid, emptyScid, bobPeer, true, false, false,
- false,
- )
- err = s.AddLink(bobChannelLink)
- require.NoError(t, err)
- // Generate preimage.
- preimage, err := genPreimage()
- require.NoError(t, err, "unable to generate preimage")
- rhash := sha256.Sum256(preimage[:])
- // Determine the outgoing SCID to use.
- outgoingSCID := aliceReal
- if useAlias {
- outgoingSCID = aliceAlias
- }
- packet := &htlcPacket{
- incomingChanID: bobScid,
- incomingHTLCID: 0,
- outgoingChanID: outgoingSCID,
- obfuscator: NewMockObfuscator(),
- htlc: &lnwire.UpdateAddHTLC{
- PaymentHash: rhash,
- Amount: 1,
- },
- }
- err = s.ForwardPackets(nil, packet)
- require.NoError(t, err)
- // If we expect a forwarding error, then assert that we receive one.
- // option_scid_alias forwards may fail if forwarding would be a privacy
- // leak.
- if expectErr {
- select {
- case <-bobChannelLink.packets:
- case <-time.After(time.Second * 5):
- t.Fatal("expected a forwarding error")
- }
- select {
- case <-aliceChannelLink.packets:
- t.Fatal("did not expect a packet")
- case <-time.After(time.Second * 5):
- }
- } else {
- select {
- case <-bobChannelLink.packets:
- t.Fatal("did not expect a forwarding error")
- case <-time.After(time.Second * 5):
- }
- select {
- case <-aliceChannelLink.packets:
- case <-time.After(time.Second * 5):
- t.Fatal("expected alice to receive packet")
- }
- }
- }
- // TestSwitchSendHTLCMapping tests that SendHTLC will properly route packets to
- // zero-conf or option-scid-alias (feature-bit) channels if the confirmed SCID
- // is used. It also tests that nothing breaks with the mapping change.
- func TestSwitchSendHTLCMapping(t *testing.T) {
- tests := []struct {
- name string
- // If this is true, the channel will be zero-conf.
- zeroConf bool
- // Denotes whether the channel is option-scid-alias, non
- // zero-conf feature bit.
- optionFeature bool
- // If this is true, then the alias will be used in the packet.
- useAlias bool
- // This will be the channel alias if there is a mapping.
- alias lnwire.ShortChannelID
- // This will be the confirmed SCID if the channel is confirmed.
- real lnwire.ShortChannelID
- }{
- {
- name: "non-zero-conf real scid w/ option",
- zeroConf: false,
- optionFeature: true,
- useAlias: false,
- alias: lnwire.ShortChannelID{
- BlockHeight: 10010,
- TxIndex: 10,
- TxPosition: 10,
- },
- real: lnwire.ShortChannelID{
- BlockHeight: 500000,
- TxIndex: 50,
- TxPosition: 50,
- },
- },
- {
- name: "non-zero-conf real scid no option",
- zeroConf: false,
- useAlias: false,
- alias: lnwire.ShortChannelID{},
- real: lnwire.ShortChannelID{
- BlockHeight: 400000,
- TxIndex: 50,
- TxPosition: 50,
- },
- },
- {
- name: "zero-conf alias scid w/ conf",
- zeroConf: true,
- useAlias: true,
- alias: lnwire.ShortChannelID{
- BlockHeight: 10020,
- TxIndex: 20,
- TxPosition: 20,
- },
- real: lnwire.ShortChannelID{
- BlockHeight: 450000,
- TxIndex: 50,
- TxPosition: 50,
- },
- },
- {
- name: "zero-conf alias scid no conf",
- zeroConf: true,
- useAlias: true,
- alias: lnwire.ShortChannelID{
- BlockHeight: 10015,
- TxIndex: 25,
- TxPosition: 35,
- },
- real: lnwire.ShortChannelID{},
- },
- {
- name: "zero-conf real scid",
- zeroConf: true,
- useAlias: false,
- alias: lnwire.ShortChannelID{
- BlockHeight: 10035,
- TxIndex: 35,
- TxPosition: 35,
- },
- real: lnwire.ShortChannelID{
- BlockHeight: 470000,
- TxIndex: 35,
- TxPosition: 45,
- },
- },
- }
- for _, test := range tests {
- test := test
- t.Run(test.name, func(t *testing.T) {
- t.Parallel()
- testSwitchSendHtlcMapping(
- t, test.zeroConf, test.useAlias, test.alias,
- test.real, test.optionFeature,
- )
- })
- }
- }
- func testSwitchSendHtlcMapping(t *testing.T, zeroConf, useAlias bool, alias,
- realScid lnwire.ShortChannelID, optionFeature bool) {
- peer, err := newMockServer(
- t, "alice", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err)
- s, err := initSwitchWithTempDB(t, testStartingHeight)
- require.NoError(t, err)
- err = s.Start()
- require.NoError(t, err)
- defer func() { _ = s.Stop() }()
- // Create the lnwire.ChannelID that we'll use.
- chanID, _ := genID()
- var link *mockChannelLink
- if zeroConf {
- link = newMockChannelLink(
- s, chanID, alias, realScid, peer, true, false, true,
- false,
- )
- } else {
- link = newMockChannelLink(
- s, chanID, realScid, emptyScid, peer, true, false,
- false, true,
- )
- if optionFeature {
- link.addAlias(alias)
- }
- }
- err = s.AddLink(link)
- require.NoError(t, err)
- // Generate preimage.
- preimage, err := genPreimage()
- require.NoError(t, err)
- rhash := sha256.Sum256(preimage[:])
- // Determine the outgoing SCID to use.
- outgoingSCID := realScid
- if useAlias {
- outgoingSCID = alias
- }
- // Send the HTLC and assert that we don't get an error.
- htlc := &lnwire.UpdateAddHTLC{
- PaymentHash: rhash,
- Amount: 1,
- }
- err = s.SendHTLC(outgoingSCID, 0, htlc)
- require.NoError(t, err)
- }
- // TestSwitchUpdateScid verifies that zero-conf and non-zero-conf
- // option-scid-alias (feature bit) channels will have the expected entries in
- // the aliasToReal and baseIndex maps.
- func TestSwitchUpdateScid(t *testing.T) {
- t.Parallel()
- peer, err := newMockServer(
- t, "alice", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err, "unable to create alice server")
- s, err := initSwitchWithTempDB(t, testStartingHeight)
- require.NoError(t, err)
- err = s.Start()
- require.NoError(t, err)
- defer func() { _ = s.Stop() }()
- // Create the IDs that we'll use.
- chanID, chanID2, _, _ := genIDs()
- alias := lnwire.ShortChannelID{
- BlockHeight: 16_000_000,
- TxIndex: 0,
- TxPosition: 0,
- }
- alias2 := alias
- alias2.TxPosition = 1
- realScid := lnwire.ShortChannelID{
- BlockHeight: 500000,
- TxIndex: 0,
- TxPosition: 0,
- }
- link := newMockChannelLink(
- s, chanID, alias, emptyScid, peer, true, false, true, false,
- )
- link.addAlias(alias2)
- err = s.AddLink(link)
- require.NoError(t, err)
- // Assert that the zero-conf link does not have entries in the
- // aliasToReal map.
- s.indexMtx.RLock()
- _, ok := s.aliasToReal[alias]
- require.False(t, ok)
- _, ok = s.aliasToReal[alias2]
- require.False(t, ok)
- // Assert that both aliases point to the "base" SCID, which is actually
- // just the first alias.
- baseScid, ok := s.baseIndex[alias]
- require.True(t, ok)
- require.Equal(t, alias, baseScid)
- baseScid, ok = s.baseIndex[alias2]
- require.True(t, ok)
- require.Equal(t, alias, baseScid)
- s.indexMtx.RUnlock()
- // We'll set the mock link's confirmed SCID so that UpdateShortChanID
- // populates aliasToReal and adds an entry to baseIndex.
- link.realScid = realScid
- link.confirmedZC = true
- err = s.UpdateShortChanID(chanID)
- require.NoError(t, err)
- // Assert that aliasToReal is populated and there is an entry in
- // baseIndex for realScid.
- s.indexMtx.RLock()
- realMapping, ok := s.aliasToReal[alias]
- require.True(t, ok)
- require.Equal(t, realScid, realMapping)
- realMapping, ok = s.aliasToReal[alias2]
- require.True(t, ok)
- require.Equal(t, realScid, realMapping)
- baseScid, ok = s.baseIndex[realScid]
- require.True(t, ok)
- require.Equal(t, alias, baseScid)
- s.indexMtx.RUnlock()
- // Now we'll perform the same checks with a non-zero-conf
- // option-scid-alias channel (feature-bit).
- optionReal := lnwire.ShortChannelID{
- BlockHeight: 600000,
- TxIndex: 0,
- TxPosition: 0,
- }
- optionAlias := lnwire.ShortChannelID{
- BlockHeight: 12000,
- TxIndex: 0,
- TxPosition: 0,
- }
- optionAlias2 := optionAlias
- optionAlias2.TxPosition = 1
- link2 := newMockChannelLink(
- s, chanID2, optionReal, emptyScid, peer, true, false, false,
- true,
- )
- link2.addAlias(optionAlias)
- link2.addAlias(optionAlias2)
- err = s.AddLink(link2)
- require.NoError(t, err)
- // Assert that the option-scid-alias link does have entries in the
- // aliasToReal and baseIndex maps.
- s.indexMtx.RLock()
- realMapping, ok = s.aliasToReal[optionAlias]
- require.True(t, ok)
- require.Equal(t, optionReal, realMapping)
- realMapping, ok = s.aliasToReal[optionAlias2]
- require.True(t, ok)
- require.Equal(t, optionReal, realMapping)
- baseScid, ok = s.baseIndex[optionReal]
- require.True(t, ok)
- require.Equal(t, optionReal, baseScid)
- baseScid, ok = s.baseIndex[optionAlias]
- require.True(t, ok)
- require.Equal(t, optionReal, baseScid)
- baseScid, ok = s.baseIndex[optionAlias2]
- require.True(t, ok)
- require.Equal(t, optionReal, baseScid)
- s.indexMtx.RUnlock()
- }
- // TestSwitchForward checks the ability of htlc switch to forward add/settle
- // requests.
- func TestSwitchForward(t *testing.T) {
- t.Parallel()
- alicePeer, err := newMockServer(
- t, "alice", testStartingHeight, nil, testDefaultDelta,
- )
- if err != nil {
- t.Fatalf("unable to create alice server: %v", err)
- }
- bobPeer, err := newMockServer(
- t, "bob", testStartingHeight, nil, testDefaultDelta,
- )
- if err != nil {
- t.Fatalf("unable to create bob server: %v", err)
- }
- s, err := initSwitchWithTempDB(t, testStartingHeight)
- if err != nil {
- t.Fatalf("unable to init switch: %v", err)
- }
- if err := s.Start(); err != nil {
- t.Fatalf("unable to start switch: %v", err)
- }
- defer s.Stop()
- chanID1, chanID2, aliceChanID, bobChanID := genIDs()
- aliceChannelLink := newMockChannelLink(
- s, chanID1, aliceChanID, emptyScid, alicePeer, true, false,
- false, false,
- )
- bobChannelLink := newMockChannelLink(
- s, chanID2, bobChanID, emptyScid, bobPeer, true, false, false,
- false,
- )
- if err := s.AddLink(aliceChannelLink); err != nil {
- t.Fatalf("unable to add alice link: %v", err)
- }
- if err := s.AddLink(bobChannelLink); err != nil {
- t.Fatalf("unable to add bob link: %v", err)
- }
- // Create request which should be forwarded from Alice channel link to
- // bob channel link.
- preimage, err := genPreimage()
- if err != nil {
- t.Fatalf("unable to generate preimage: %v", err)
- }
- rhash := sha256.Sum256(preimage[:])
- packet := &htlcPacket{
- incomingChanID: aliceChannelLink.ShortChanID(),
- incomingHTLCID: 0,
- outgoingChanID: bobChannelLink.ShortChanID(),
- obfuscator: NewMockObfuscator(),
- htlc: &lnwire.UpdateAddHTLC{
- PaymentHash: rhash,
- Amount: 1,
- },
- }
- // Handle the request and checks that bob channel link received it.
- if err := s.ForwardPackets(nil, packet); err != nil {
- t.Fatal(err)
- }
- select {
- case <-bobChannelLink.packets:
- if err := bobChannelLink.completeCircuit(packet); err != nil {
- t.Fatalf("unable to complete payment circuit: %v", err)
- }
- case <-time.After(time.Second):
- t.Fatal("request was not propagated to destination")
- }
- if s.circuits.NumOpen() != 1 {
- t.Fatal("wrong amount of circuits")
- }
- if !s.IsForwardedHTLC(bobChannelLink.ShortChanID(), 0) {
- t.Fatal("htlc should be identified as forwarded")
- }
- // Create settle request pretending that bob link handled the add htlc
- // request and sent the htlc settle request back. This request should
- // be forwarder back to Alice link.
- packet = &htlcPacket{
- outgoingChanID: bobChannelLink.ShortChanID(),
- outgoingHTLCID: 0,
- amount: 1,
- htlc: &lnwire.UpdateFulfillHTLC{
- PaymentPreimage: preimage,
- },
- }
- // Handle the request and checks that payment circuit works properly.
- if err := s.ForwardPackets(nil, packet); err != nil {
- t.Fatal(err)
- }
- select {
- case pkt := <-aliceChannelLink.packets:
- if err := aliceChannelLink.deleteCircuit(pkt); err != nil {
- t.Fatalf("unable to remove circuit: %v", err)
- }
- case <-time.After(time.Second):
- t.Fatal("request was not propagated to channelPoint")
- }
- if s.circuits.NumOpen() != 0 {
- t.Fatal("wrong amount of circuits")
- }
- }
- func TestSwitchForwardFailAfterFullAdd(t *testing.T) {
- t.Parallel()
- chanID1, chanID2, aliceChanID, bobChanID := genIDs()
- alicePeer, err := newMockServer(
- t, "alice", testStartingHeight, nil, testDefaultDelta,
- )
- if err != nil {
- t.Fatalf("unable to create alice server: %v", err)
- }
- bobPeer, err := newMockServer(
- t, "bob", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err, "unable to create bob server")
- tempPath := t.TempDir()
- cdb, err := channeldb.Open(tempPath)
- require.NoError(t, err, "unable to open channeldb")
- t.Cleanup(func() { cdb.Close() })
- s, err := initSwitchWithDB(testStartingHeight, cdb)
- require.NoError(t, err, "unable to init switch")
- if err := s.Start(); err != nil {
- t.Fatalf("unable to start switch: %v", err)
- }
- // Even though we intend to Stop s later in the test, it is safe to
- // defer this Stop since its execution it is protected by an atomic
- // guard, guaranteeing it executes at most once.
- defer s.Stop()
- aliceChannelLink := newMockChannelLink(
- s, chanID1, aliceChanID, emptyScid, alicePeer, true, false,
- false, false,
- )
- bobChannelLink := newMockChannelLink(
- s, chanID2, bobChanID, emptyScid, bobPeer, true, false, false,
- false,
- )
- if err := s.AddLink(aliceChannelLink); err != nil {
- t.Fatalf("unable to add alice link: %v", err)
- }
- if err := s.AddLink(bobChannelLink); err != nil {
- t.Fatalf("unable to add bob link: %v", err)
- }
- // Create request which should be forwarded from Alice channel link to
- // bob channel link.
- preimage := [sha256.Size]byte{1}
- rhash := sha256.Sum256(preimage[:])
- ogPacket := &htlcPacket{
- incomingChanID: aliceChannelLink.ShortChanID(),
- incomingHTLCID: 0,
- outgoingChanID: bobChannelLink.ShortChanID(),
- obfuscator: NewMockObfuscator(),
- htlc: &lnwire.UpdateAddHTLC{
- PaymentHash: rhash,
- Amount: 1,
- },
- }
- if s.circuits.NumPending() != 0 {
- t.Fatalf("wrong amount of half circuits")
- }
- if s.circuits.NumOpen() != 0 {
- t.Fatalf("wrong amount of circuits")
- }
- // Handle the request and checks that bob channel link received it.
- if err := s.ForwardPackets(nil, ogPacket); err != nil {
- t.Fatal(err)
- }
- if s.circuits.NumPending() != 1 {
- t.Fatalf("wrong amount of half circuits")
- }
- if s.circuits.NumOpen() != 0 {
- t.Fatalf("wrong amount of circuits")
- }
- // Pull packet from bob's link, but do not perform a full add.
- select {
- case packet := <-bobChannelLink.packets:
- // Complete the payment circuit and assign the outgoing htlc id
- // before restarting.
- if err := bobChannelLink.completeCircuit(packet); err != nil {
- t.Fatalf("unable to complete payment circuit: %v", err)
- }
- case <-time.After(time.Second):
- t.Fatal("request was not propagated to destination")
- }
- if s.circuits.NumPending() != 1 {
- t.Fatalf("wrong amount of half circuits")
- }
- if s.circuits.NumOpen() != 1 {
- t.Fatalf("wrong amount of circuits")
- }
- // Now we will restart bob, leaving the forwarding decision for this
- // htlc is in the half-added state.
- if err := s.Stop(); err != nil {
- t.Fatalf(err.Error())
- }
- if err := cdb.Close(); err != nil {
- t.Fatalf(err.Error())
- }
- cdb2, err := channeldb.Open(tempPath)
- require.NoError(t, err, "unable to reopen channeldb")
- t.Cleanup(func() { cdb2.Close() })
- s2, err := initSwitchWithDB(testStartingHeight, cdb2)
- require.NoError(t, err, "unable reinit switch")
- if err := s2.Start(); err != nil {
- t.Fatalf("unable to restart switch: %v", err)
- }
- // Even though we intend to Stop s2 later in the test, it is safe to
- // defer this Stop since its execution it is protected by an atomic
- // guard, guaranteeing it executes at most once.
- defer s2.Stop()
- aliceChannelLink = newMockChannelLink(
- s2, chanID1, aliceChanID, emptyScid, alicePeer, true, false,
- false, false,
- )
- bobChannelLink = newMockChannelLink(
- s2, chanID2, bobChanID, emptyScid, bobPeer, true, false, false,
- false,
- )
- if err := s2.AddLink(aliceChannelLink); err != nil {
- t.Fatalf("unable to add alice link: %v", err)
- }
- if err := s2.AddLink(bobChannelLink); err != nil {
- t.Fatalf("unable to add bob link: %v", err)
- }
- if s2.circuits.NumPending() != 1 {
- t.Fatalf("wrong amount of half circuits")
- }
- if s2.circuits.NumOpen() != 1 {
- t.Fatalf("wrong amount of circuits")
- }
- // Craft a failure message from the remote peer.
- fail := &htlcPacket{
- outgoingChanID: bobChannelLink.ShortChanID(),
- outgoingHTLCID: 0,
- amount: 1,
- htlc: &lnwire.UpdateFailHTLC{},
- }
- // Send the fail packet from the remote peer through the switch.
- if err := s2.ForwardPackets(nil, fail); err != nil {
- t.Fatalf(err.Error())
- }
- // Pull packet from alice's link, as it should have gone through
- // successfully.
- select {
- case pkt := <-aliceChannelLink.packets:
- if err := aliceChannelLink.completeCircuit(pkt); err != nil {
- t.Fatalf("unable to remove circuit: %v", err)
- }
- case <-time.After(time.Second):
- t.Fatal("request was not propagated to destination")
- }
- // Circuit map should be empty now.
- if s2.circuits.NumPending() != 0 {
- t.Fatalf("wrong amount of half circuits")
- }
- if s2.circuits.NumOpen() != 0 {
- t.Fatalf("wrong amount of circuits")
- }
- // Send the fail packet from the remote peer through the switch.
- if err := s.ForwardPackets(nil, fail); err != nil {
- t.Fatal(err)
- }
- select {
- case <-aliceChannelLink.packets:
- t.Fatalf("expected duplicate fail to not arrive at the destination")
- case <-time.After(time.Second):
- }
- }
- func TestSwitchForwardSettleAfterFullAdd(t *testing.T) {
- t.Parallel()
- chanID1, chanID2, aliceChanID, bobChanID := genIDs()
- alicePeer, err := newMockServer(
- t, "alice", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err, "unable to create alice server")
- bobPeer, err := newMockServer(
- t, "bob", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err, "unable to create bob server")
- tempPath := t.TempDir()
- cdb, err := channeldb.Open(tempPath)
- require.NoError(t, err, "unable to open channeldb")
- t.Cleanup(func() { cdb.Close() })
- s, err := initSwitchWithDB(testStartingHeight, cdb)
- require.NoError(t, err, "unable to init switch")
- if err := s.Start(); err != nil {
- t.Fatalf("unable to start switch: %v", err)
- }
- // Even though we intend to Stop s later in the test, it is safe to
- // defer this Stop since its execution it is protected by an atomic
- // guard, guaranteeing it executes at most once.
- defer s.Stop()
- aliceChannelLink := newMockChannelLink(
- s, chanID1, aliceChanID, emptyScid, alicePeer, true, false,
- false, false,
- )
- bobChannelLink := newMockChannelLink(
- s, chanID2, bobChanID, emptyScid, bobPeer, true, false, false,
- false,
- )
- if err := s.AddLink(aliceChannelLink); err != nil {
- t.Fatalf("unable to add alice link: %v", err)
- }
- if err := s.AddLink(bobChannelLink); err != nil {
- t.Fatalf("unable to add bob link: %v", err)
- }
- // Create request which should be forwarded from Alice channel link to
- // bob channel link.
- preimage := [sha256.Size]byte{1}
- rhash := sha256.Sum256(preimage[:])
- ogPacket := &htlcPacket{
- incomingChanID: aliceChannelLink.ShortChanID(),
- incomingHTLCID: 0,
- outgoingChanID: bobChannelLink.ShortChanID(),
- obfuscator: NewMockObfuscator(),
- htlc: &lnwire.UpdateAddHTLC{
- PaymentHash: rhash,
- Amount: 1,
- },
- }
- if s.circuits.NumPending() != 0 {
- t.Fatalf("wrong amount of half circuits")
- }
- if s.circuits.NumOpen() != 0 {
- t.Fatalf("wrong amount of circuits")
- }
- // Handle the request and checks that bob channel link received it.
- if err := s.ForwardPackets(nil, ogPacket); err != nil {
- t.Fatal(err)
- }
- if s.circuits.NumPending() != 1 {
- t.Fatalf("wrong amount of half circuits")
- }
- if s.circuits.NumOpen() != 0 {
- t.Fatalf("wrong amount of circuits")
- }
- // Pull packet from bob's link, but do not perform a full add.
- select {
- case packet := <-bobChannelLink.packets:
- // Complete the payment circuit and assign the outgoing htlc id
- // before restarting.
- if err := bobChannelLink.completeCircuit(packet); err != nil {
- t.Fatalf("unable to complete payment circuit: %v", err)
- }
- case <-time.After(time.Second):
- t.Fatal("request was not propagated to destination")
- }
- if s.circuits.NumPending() != 1 {
- t.Fatalf("wrong amount of half circuits")
- }
- if s.circuits.NumOpen() != 1 {
- t.Fatalf("wrong amount of circuits")
- }
- // Now we will restart bob, leaving the forwarding decision for this
- // htlc is in the half-added state.
- if err := s.Stop(); err != nil {
- t.Fatalf(err.Error())
- }
- if err := cdb.Close(); err != nil {
- t.Fatalf(err.Error())
- }
- cdb2, err := channeldb.Open(tempPath)
- require.NoError(t, err, "unable to reopen channeldb")
- t.Cleanup(func() { cdb2.Close() })
- s2, err := initSwitchWithDB(testStartingHeight, cdb2)
- require.NoError(t, err, "unable reinit switch")
- if err := s2.Start(); err != nil {
- t.Fatalf("unable to restart switch: %v", err)
- }
- // Even though we intend to Stop s2 later in the test, it is safe to
- // defer this Stop since its execution it is protected by an atomic
- // guard, guaranteeing it executes at most once.
- defer s2.Stop()
- aliceChannelLink = newMockChannelLink(
- s2, chanID1, aliceChanID, emptyScid, alicePeer, true, false,
- false, false,
- )
- bobChannelLink = newMockChannelLink(
- s2, chanID2, bobChanID, emptyScid, bobPeer, true, false, false,
- false,
- )
- if err := s2.AddLink(aliceChannelLink); err != nil {
- t.Fatalf("unable to add alice link: %v", err)
- }
- if err := s2.AddLink(bobChannelLink); err != nil {
- t.Fatalf("unable to add bob link: %v", err)
- }
- if s2.circuits.NumPending() != 1 {
- t.Fatalf("wrong amount of half circuits")
- }
- if s2.circuits.NumOpen() != 1 {
- t.Fatalf("wrong amount of circuits")
- }
- // Craft a settle message from the remote peer.
- settle := &htlcPacket{
- outgoingChanID: bobChannelLink.ShortChanID(),
- outgoingHTLCID: 0,
- amount: 1,
- htlc: &lnwire.UpdateFulfillHTLC{
- PaymentPreimage: preimage,
- },
- }
- // Send the settle packet from the remote peer through the switch.
- if err := s2.ForwardPackets(nil, settle); err != nil {
- t.Fatalf(err.Error())
- }
- // Pull packet from alice's link, as it should have gone through
- // successfully.
- select {
- case packet := <-aliceChannelLink.packets:
- if err := aliceChannelLink.completeCircuit(packet); err != nil {
- t.Fatalf("unable to complete circuit with in key=%s: %v",
- packet.inKey(), err)
- }
- case <-time.After(time.Second):
- t.Fatal("request was not propagated to destination")
- }
- // Circuit map should be empty now.
- if s2.circuits.NumPending() != 0 {
- t.Fatalf("wrong amount of half circuits")
- }
- if s2.circuits.NumOpen() != 0 {
- t.Fatalf("wrong amount of circuits")
- }
- // Send the settle packet again, which not arrive at destination.
- if err := s2.ForwardPackets(nil, settle); err != nil {
- t.Fatal(err)
- }
- select {
- case <-bobChannelLink.packets:
- t.Fatalf("expected duplicate fail to not arrive at the destination")
- case <-time.After(time.Second):
- }
- }
- func TestSwitchForwardDropAfterFullAdd(t *testing.T) {
- t.Parallel()
- chanID1, chanID2, aliceChanID, bobChanID := genIDs()
- alicePeer, err := newMockServer(
- t, "alice", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err, "unable to create alice server")
- bobPeer, err := newMockServer(
- t, "bob", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err, "unable to create bob server")
- tempPath := t.TempDir()
- cdb, err := channeldb.Open(tempPath)
- require.NoError(t, err, "unable to open channeldb")
- t.Cleanup(func() { cdb.Close() })
- s, err := initSwitchWithDB(testStartingHeight, cdb)
- require.NoError(t, err, "unable to init switch")
- if err := s.Start(); err != nil {
- t.Fatalf("unable to start switch: %v", err)
- }
- // Even though we intend to Stop s later in the test, it is safe to
- // defer this Stop since its execution it is protected by an atomic
- // guard, guaranteeing it executes at most once.
- defer s.Stop()
- aliceChannelLink := newMockChannelLink(
- s, chanID1, aliceChanID, emptyScid, alicePeer, true, false,
- false, false,
- )
- bobChannelLink := newMockChannelLink(
- s, chanID2, bobChanID, emptyScid, bobPeer, true, false, false,
- false,
- )
- if err := s.AddLink(aliceChannelLink); err != nil {
- t.Fatalf("unable to add alice link: %v", err)
- }
- if err := s.AddLink(bobChannelLink); err != nil {
- t.Fatalf("unable to add bob link: %v", err)
- }
- // Create request which should be forwarded from Alice channel link to
- // bob channel link.
- preimage := [sha256.Size]byte{1}
- rhash := sha256.Sum256(preimage[:])
- ogPacket := &htlcPacket{
- incomingChanID: aliceChannelLink.ShortChanID(),
- incomingHTLCID: 0,
- outgoingChanID: bobChannelLink.ShortChanID(),
- obfuscator: NewMockObfuscator(),
- htlc: &lnwire.UpdateAddHTLC{
- PaymentHash: rhash,
- Amount: 1,
- },
- }
- if s.circuits.NumPending() != 0 {
- t.Fatalf("wrong amount of half circuits")
- }
- if s.circuits.NumOpen() != 0 {
- t.Fatalf("wrong amount of circuits")
- }
- // Handle the request and checks that bob channel link received it.
- if err := s.ForwardPackets(nil, ogPacket); err != nil {
- t.Fatal(err)
- }
- if s.circuits.NumPending() != 1 {
- t.Fatalf("wrong amount of half circuits")
- }
- if s.circuits.NumOpen() != 0 {
- t.Fatalf("wrong amount of half circuits")
- }
- // Pull packet from bob's link, but do not perform a full add.
- select {
- case packet := <-bobChannelLink.packets:
- // Complete the payment circuit and assign the outgoing htlc id
- // before restarting.
- if err := bobChannelLink.completeCircuit(packet); err != nil {
- t.Fatalf("unable to complete payment circuit: %v", err)
- }
- case <-time.After(time.Second):
- t.Fatal("request was not propagated to destination")
- }
- // Now we will restart bob, leaving the forwarding decision for this
- // htlc is in the half-added state.
- if err := s.Stop(); err != nil {
- t.Fatalf(err.Error())
- }
- if err := cdb.Close(); err != nil {
- t.Fatalf(err.Error())
- }
- cdb2, err := channeldb.Open(tempPath)
- require.NoError(t, err, "unable to reopen channeldb")
- t.Cleanup(func() { cdb2.Close() })
- s2, err := initSwitchWithDB(testStartingHeight, cdb2)
- require.NoError(t, err, "unable reinit switch")
- if err := s2.Start(); err != nil {
- t.Fatalf("unable to restart switch: %v", err)
- }
- // Even though we intend to Stop s2 later in the test, it is safe to
- // defer this Stop since its execution it is protected by an atomic
- // guard, guaranteeing it executes at most once.
- defer s2.Stop()
- aliceChannelLink = newMockChannelLink(
- s2, chanID1, aliceChanID, emptyScid, alicePeer, true, false,
- false, false,
- )
- bobChannelLink = newMockChannelLink(
- s2, chanID2, bobChanID, emptyScid, bobPeer, true, false, false,
- false,
- )
- if err := s2.AddLink(aliceChannelLink); err != nil {
- t.Fatalf("unable to add alice link: %v", err)
- }
- if err := s2.AddLink(bobChannelLink); err != nil {
- t.Fatalf("unable to add bob link: %v", err)
- }
- if s2.circuits.NumPending() != 1 {
- t.Fatalf("wrong amount of half circuits")
- }
- if s2.circuits.NumOpen() != 1 {
- t.Fatalf("wrong amount of half circuits")
- }
- // Resend the failed htlc. The packet will be dropped silently since the
- // switch will detect that it has been half added previously.
- if err := s2.ForwardPackets(nil, ogPacket); err != nil {
- t.Fatal(err)
- }
- // After detecting an incomplete forward, the fail packet should have
- // been returned to the sender.
- select {
- case <-aliceChannelLink.packets:
- t.Fatal("request should not have returned to source")
- case <-bobChannelLink.packets:
- t.Fatal("request should not have forwarded to destination")
- case <-time.After(time.Second):
- }
- }
- func TestSwitchForwardFailAfterHalfAdd(t *testing.T) {
- t.Parallel()
- chanID1, chanID2, aliceChanID, bobChanID := genIDs()
- alicePeer, err := newMockServer(
- t, "alice", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err, "unable to create alice server")
- bobPeer, err := newMockServer(
- t, "bob", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err, "unable to create bob server")
- tempPath := t.TempDir()
- cdb, err := channeldb.Open(tempPath)
- require.NoError(t, err, "unable to open channeldb")
- t.Cleanup(func() { cdb.Close() })
- s, err := initSwitchWithDB(testStartingHeight, cdb)
- require.NoError(t, err, "unable to init switch")
- if err := s.Start(); err != nil {
- t.Fatalf("unable to start switch: %v", err)
- }
- // Even though we intend to Stop s later in the test, it is safe to
- // defer this Stop since its execution it is protected by an atomic
- // guard, guaranteeing it executes at most once.
- defer s.Stop()
- aliceChannelLink := newMockChannelLink(
- s, chanID1, aliceChanID, emptyScid, alicePeer, true, false,
- false, false,
- )
- bobChannelLink := newMockChannelLink(
- s, chanID2, bobChanID, emptyScid, bobPeer, true, false, false,
- false,
- )
- if err := s.AddLink(aliceChannelLink); err != nil {
- t.Fatalf("unable to add alice link: %v", err)
- }
- if err := s.AddLink(bobChannelLink); err != nil {
- t.Fatalf("unable to add bob link: %v", err)
- }
- // Create request which should be forwarded from Alice channel link to
- // bob channel link.
- preimage := [sha256.Size]byte{1}
- rhash := sha256.Sum256(preimage[:])
- ogPacket := &htlcPacket{
- incomingChanID: aliceChannelLink.ShortChanID(),
- incomingHTLCID: 0,
- outgoingChanID: bobChannelLink.ShortChanID(),
- obfuscator: NewMockObfuscator(),
- htlc: &lnwire.UpdateAddHTLC{
- PaymentHash: rhash,
- Amount: 1,
- },
- }
- if s.circuits.NumPending() != 0 {
- t.Fatalf("wrong amount of half circuits")
- }
- if s.circuits.NumOpen() != 0 {
- t.Fatalf("wrong amount of circuits")
- }
- // Handle the request and checks that bob channel link received it.
- if err := s.ForwardPackets(nil, ogPacket); err != nil {
- t.Fatal(err)
- }
- if s.circuits.NumPending() != 1 {
- t.Fatalf("wrong amount of half circuits")
- }
- if s.circuits.NumOpen() != 0 {
- t.Fatalf("wrong amount of half circuits")
- }
- // Pull packet from bob's link, but do not perform a full add.
- select {
- case <-bobChannelLink.packets:
- case <-time.After(time.Second):
- t.Fatal("request was not propagated to destination")
- }
- // Now we will restart bob, leaving the forwarding decision for this
- // htlc is in the half-added state.
- if err := s.Stop(); err != nil {
- t.Fatalf(err.Error())
- }
- if err := cdb.Close(); err != nil {
- t.Fatalf(err.Error())
- }
- cdb2, err := channeldb.Open(tempPath)
- require.NoError(t, err, "unable to reopen channeldb")
- t.Cleanup(func() { cdb2.Close() })
- s2, err := initSwitchWithDB(testStartingHeight, cdb2)
- require.NoError(t, err, "unable reinit switch")
- if err := s2.Start(); err != nil {
- t.Fatalf("unable to restart switch: %v", err)
- }
- // Even though we intend to Stop s2 later in the test, it is safe to
- // defer this Stop since its execution it is protected by an atomic
- // guard, guaranteeing it executes at most once.
- defer s2.Stop()
- aliceChannelLink = newMockChannelLink(
- s2, chanID1, aliceChanID, emptyScid, alicePeer, true, false,
- false, false,
- )
- bobChannelLink = newMockChannelLink(
- s2, chanID2, bobChanID, emptyScid, bobPeer, true, false, false,
- false,
- )
- if err := s2.AddLink(aliceChannelLink); err != nil {
- t.Fatalf("unable to add alice link: %v", err)
- }
- if err := s2.AddLink(bobChannelLink); err != nil {
- t.Fatalf("unable to add bob link: %v", err)
- }
- if s2.circuits.NumPending() != 1 {
- t.Fatalf("wrong amount of half circuits")
- }
- if s2.circuits.NumOpen() != 0 {
- t.Fatalf("wrong amount of half circuits")
- }
- // Resend the failed htlc, it should be returned to alice since the
- // switch will detect that it has been half added previously.
- err = s2.ForwardPackets(nil, ogPacket)
- if err != nil {
- t.Fatal(err)
- }
- // After detecting an incomplete forward, the fail packet should have
- // been returned to the sender.
- select {
- case pkt := <-aliceChannelLink.packets:
- linkErr := pkt.linkFailure
- if linkErr.FailureDetail != OutgoingFailureIncompleteForward {
- t.Fatalf("expected incomplete forward, got: %v",
- linkErr.FailureDetail)
- }
- case <-time.After(time.Second):
- t.Fatal("request was not propagated to destination")
- }
- }
- // TestSwitchForwardCircuitPersistence checks the ability of htlc switch to
- // maintain the proper entries in the circuit map in the face of restarts.
- func TestSwitchForwardCircuitPersistence(t *testing.T) {
- t.Parallel()
- chanID1, chanID2, aliceChanID, bobChanID := genIDs()
- alicePeer, err := newMockServer(
- t, "alice", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err, "unable to create alice server")
- bobPeer, err := newMockServer(
- t, "bob", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err, "unable to create bob server")
- tempPath := t.TempDir()
- cdb, err := channeldb.Open(tempPath)
- require.NoError(t, err, "unable to open channeldb")
- t.Cleanup(func() { cdb.Close() })
- s, err := initSwitchWithDB(testStartingHeight, cdb)
- require.NoError(t, err, "unable to init switch")
- if err := s.Start(); err != nil {
- t.Fatalf("unable to start switch: %v", err)
- }
- // Even though we intend to Stop s later in the test, it is safe to
- // defer this Stop since its execution it is protected by an atomic
- // guard, guaranteeing it executes at most once.
- defer s.Stop()
- aliceChannelLink := newMockChannelLink(
- s, chanID1, aliceChanID, emptyScid, alicePeer, true, false,
- false, false,
- )
- bobChannelLink := newMockChannelLink(
- s, chanID2, bobChanID, emptyScid, bobPeer, true, false, false,
- false,
- )
- if err := s.AddLink(aliceChannelLink); err != nil {
- t.Fatalf("unable to add alice link: %v", err)
- }
- if err := s.AddLink(bobChannelLink); err != nil {
- t.Fatalf("unable to add bob link: %v", err)
- }
- // Create request which should be forwarded from Alice channel link to
- // bob channel link.
- preimage := [sha256.Size]byte{1}
- rhash := sha256.Sum256(preimage[:])
- ogPacket := &htlcPacket{
- incomingChanID: aliceChannelLink.ShortChanID(),
- incomingHTLCID: 0,
- outgoingChanID: bobChannelLink.ShortChanID(),
- obfuscator: NewMockObfuscator(),
- htlc: &lnwire.UpdateAddHTLC{
- PaymentHash: rhash,
- Amount: 1,
- },
- }
- if s.circuits.NumPending() != 0 {
- t.Fatalf("wrong amount of half circuits")
- }
- if s.circuits.NumOpen() != 0 {
- t.Fatalf("wrong amount of circuits")
- }
- // Handle the request and checks that bob channel link received it.
- if err := s.ForwardPackets(nil, ogPacket); err != nil {
- t.Fatal(err)
- }
- if s.circuits.NumPending() != 1 {
- t.Fatalf("wrong amount of half circuits")
- }
- if s.circuits.NumOpen() != 0 {
- t.Fatalf("wrong amount of circuits")
- }
- // Retrieve packet from outgoing link and cache until after restart.
- var packet *htlcPacket
- select {
- case packet = <-bobChannelLink.packets:
- case <-time.After(time.Second):
- t.Fatal("request was not propagated to destination")
- }
- if err := s.Stop(); err != nil {
- t.Fatalf(err.Error())
- }
- if err := cdb.Close(); err != nil {
- t.Fatalf(err.Error())
- }
- cdb2, err := channeldb.Open(tempPath)
- require.NoError(t, err, "unable to reopen channeldb")
- t.Cleanup(func() { cdb2.Close() })
- s2, err := initSwitchWithDB(testStartingHeight, cdb2)
- require.NoError(t, err, "unable reinit switch")
- if err := s2.Start(); err != nil {
- t.Fatalf("unable to restart switch: %v", err)
- }
- // Even though we intend to Stop s2 later in the test, it is safe to
- // defer this Stop since its execution it is protected by an atomic
- // guard, guaranteeing it executes at most once.
- defer s2.Stop()
- aliceChannelLink = newMockChannelLink(
- s2, chanID1, aliceChanID, emptyScid, alicePeer, true, false,
- false, false,
- )
- bobChannelLink = newMockChannelLink(
- s2, chanID2, bobChanID, emptyScid, bobPeer, true, false, false,
- false,
- )
- if err := s2.AddLink(aliceChannelLink); err != nil {
- t.Fatalf("unable to add alice link: %v", err)
- }
- if err := s2.AddLink(bobChannelLink); err != nil {
- t.Fatalf("unable to add bob link: %v", err)
- }
- if s2.circuits.NumPending() != 1 {
- t.Fatalf("wrong amount of half circuits")
- }
- if s2.circuits.NumOpen() != 0 {
- t.Fatalf("wrong amount of half circuits")
- }
- // Now that the switch has restarted, complete the payment circuit.
- if err := bobChannelLink.completeCircuit(packet); err != nil {
- t.Fatalf("unable to complete payment circuit: %v", err)
- }
- if s2.circuits.NumPending() != 1 {
- t.Fatalf("wrong amount of half circuits")
- }
- if s2.circuits.NumOpen() != 1 {
- t.Fatal("wrong amount of circuits")
- }
- // Create settle request pretending that bob link handled the add htlc
- // request and sent the htlc settle request back. This request should
- // be forwarder back to Alice link.
- ogPacket = &htlcPacket{
- outgoingChanID: bobChannelLink.ShortChanID(),
- outgoingHTLCID: 0,
- amount: 1,
- htlc: &lnwire.UpdateFulfillHTLC{
- PaymentPreimage: preimage,
- },
- }
- // Handle the request and checks that payment circuit works properly.
- if err := s2.ForwardPackets(nil, ogPacket); err != nil {
- t.Fatal(err)
- }
- select {
- case packet = <-aliceChannelLink.packets:
- if err := aliceChannelLink.completeCircuit(packet); err != nil {
- t.Fatalf("unable to complete circuit with in key=%s: %v",
- packet.inKey(), err)
- }
- case <-time.After(time.Second):
- t.Fatal("request was not propagated to channelPoint")
- }
- if s2.circuits.NumPending() != 0 {
- t.Fatalf("wrong amount of half circuits, want 1, got %d",
- s2.circuits.NumPending())
- }
- if s2.circuits.NumOpen() != 0 {
- t.Fatal("wrong amount of circuits")
- }
- if err := s2.Stop(); err != nil {
- t.Fatal(err)
- }
- if err := cdb2.Close(); err != nil {
- t.Fatalf(err.Error())
- }
- cdb3, err := channeldb.Open(tempPath)
- require.NoError(t, err, "unable to reopen channeldb")
- t.Cleanup(func() { cdb3.Close() })
- s3, err := initSwitchWithDB(testStartingHeight, cdb3)
- require.NoError(t, err, "unable reinit switch")
- if err := s3.Start(); err != nil {
- t.Fatalf("unable to restart switch: %v", err)
- }
- defer s3.Stop()
- aliceChannelLink = newMockChannelLink(
- s3, chanID1, aliceChanID, emptyScid, alicePeer, true, false,
- false, false,
- )
- bobChannelLink = newMockChannelLink(
- s3, chanID2, bobChanID, emptyScid, bobPeer, true, false, false,
- false,
- )
- if err := s3.AddLink(aliceChannelLink); err != nil {
- t.Fatalf("unable to add alice link: %v", err)
- }
- if err := s3.AddLink(bobChannelLink); err != nil {
- t.Fatalf("unable to add bob link: %v", err)
- }
- if s3.circuits.NumPending() != 0 {
- t.Fatalf("wrong amount of half circuits")
- }
- if s3.circuits.NumOpen() != 0 {
- t.Fatalf("wrong amount of circuits")
- }
- }
- type multiHopFwdTest struct {
- name string
- eligible1, eligible2 bool
- failure1, failure2 *LinkError
- expectedReply lnwire.FailCode
- }
- // TestCircularForwards tests the allowing/disallowing of circular payments
- // through the same channel in the case where the switch is configured to allow
- // and disallow same channel circular forwards.
- func TestCircularForwards(t *testing.T) {
- chanID1, aliceChanID := genID()
- preimage := [sha256.Size]byte{1}
- hash := sha256.Sum256(preimage[:])
- tests := []struct {
- name string
- allowCircularPayment bool
- expectedErr error
- }{
- {
- name: "circular payment allowed",
- allowCircularPayment: true,
- expectedErr: nil,
- },
- {
- name: "circular payment disallowed",
- allowCircularPayment: false,
- expectedErr: NewDetailedLinkError(
- lnwire.NewTemporaryChannelFailure(nil),
- OutgoingFailureCircularRoute,
- ),
- },
- }
- for _, test := range tests {
- test := test
- t.Run(test.name, func(t *testing.T) {
- t.Parallel()
- alicePeer, err := newMockServer(
- t, "alice", testStartingHeight, nil,
- testDefaultDelta,
- )
- if err != nil {
- t.Fatalf("unable to create alice server: %v",
- err)
- }
- s, err := initSwitchWithTempDB(t, testStartingHeight)
- if err != nil {
- t.Fatalf("unable to init switch: %v", err)
- }
- if err := s.Start(); err != nil {
- t.Fatalf("unable to start switch: %v", err)
- }
- defer func() { _ = s.Stop() }()
- // Set the switch to allow or disallow circular routes
- // according to the test's requirements.
- s.cfg.AllowCircularRoute = test.allowCircularPayment
- aliceChannelLink := newMockChannelLink(
- s, chanID1, aliceChanID, emptyScid, alicePeer,
- true, false, false, false,
- )
- if err := s.AddLink(aliceChannelLink); err != nil {
- t.Fatalf("unable to add alice link: %v", err)
- }
- // Create a new packet that loops through alice's link
- // in a circle.
- obfuscator := NewMockObfuscator()
- packet := &htlcPacket{
- incomingChanID: aliceChannelLink.ShortChanID(),
- outgoingChanID: aliceChannelLink.ShortChanID(),
- htlc: &lnwire.UpdateAddHTLC{
- PaymentHash: hash,
- Amount: 1,
- },
- obfuscator: obfuscator,
- }
- // Attempt to forward the packet and check for the expected
- // error.
- if err = s.ForwardPackets(nil, packet); err != nil {
- t.Fatal(err)
- }
- select {
- case p := <-aliceChannelLink.packets:
- if p.linkFailure != nil {
- err = p.linkFailure
- }
- case <-time.After(time.Second):
- t.Fatal("no timely reply from switch")
- }
- if !reflect.DeepEqual(err, test.expectedErr) {
- t.Fatalf("expected: %v, got: %v",
- test.expectedErr, err)
- }
- // Ensure that no circuits were opened.
- if s.circuits.NumOpen() > 0 {
- t.Fatal("do not expect any open circuits")
- }
- })
- }
- }
- // TestCheckCircularForward tests the error returned by checkCircularForward
- // in cases where we allow and disallow same channel circular forwards.
- func TestCheckCircularForward(t *testing.T) {
- tests := []struct {
- name string
- // aliasMapping determines whether the test should add an alias
- // mapping to Switch alias maps before checkCircularForward.
- aliasMapping bool
- // allowCircular determines whether we should allow circular
- // forwards.
- allowCircular bool
- // incomingLink is the link that the htlc arrived on.
- incomingLink lnwire.ShortChannelID
- // outgoingLink is the link that the htlc forward
- // is destined to leave on.
- outgoingLink lnwire.ShortChannelID
- // expectedErr is the error we expect to be returned.
- expectedErr *LinkError
- }{
- {
- name: "not circular, allowed in config",
- aliasMapping: false,
- allowCircular: true,
- incomingLink: lnwire.NewShortChanIDFromInt(123),
- outgoingLink: lnwire.NewShortChanIDFromInt(321),
- expectedErr: nil,
- },
- {
- name: "not circular, not allowed in config",
- aliasMapping: false,
- allowCircular: false,
- incomingLink: lnwire.NewShortChanIDFromInt(123),
- outgoingLink: lnwire.NewShortChanIDFromInt(321),
- expectedErr: nil,
- },
- {
- name: "circular, allowed in config",
- aliasMapping: false,
- allowCircular: true,
- incomingLink: lnwire.NewShortChanIDFromInt(123),
- outgoingLink: lnwire.NewShortChanIDFromInt(123),
- expectedErr: nil,
- },
- {
- name: "circular, not allowed in config",
- aliasMapping: false,
- allowCircular: false,
- incomingLink: lnwire.NewShortChanIDFromInt(123),
- outgoingLink: lnwire.NewShortChanIDFromInt(123),
- expectedErr: NewDetailedLinkError(
- lnwire.NewTemporaryChannelFailure(nil),
- OutgoingFailureCircularRoute,
- ),
- },
- {
- name: "circular with map, not allowed",
- aliasMapping: true,
- allowCircular: false,
- incomingLink: lnwire.NewShortChanIDFromInt(1 << 60),
- outgoingLink: lnwire.NewShortChanIDFromInt(1 << 55),
- expectedErr: NewDetailedLinkError(
- lnwire.NewTemporaryChannelFailure(nil),
- OutgoingFailureCircularRoute,
- ),
- },
- {
- name: "circular with map, not allowed 2",
- aliasMapping: true,
- allowCircular: false,
- incomingLink: lnwire.NewShortChanIDFromInt(1 << 55),
- outgoingLink: lnwire.NewShortChanIDFromInt(1 << 60),
- expectedErr: NewDetailedLinkError(
- lnwire.NewTemporaryChannelFailure(nil),
- OutgoingFailureCircularRoute,
- ),
- },
- {
- name: "circular with map, allowed",
- aliasMapping: true,
- allowCircular: true,
- incomingLink: lnwire.NewShortChanIDFromInt(1 << 60),
- outgoingLink: lnwire.NewShortChanIDFromInt(1 << 55),
- expectedErr: nil,
- },
- {
- name: "circular with map, allowed 2",
- aliasMapping: true,
- allowCircular: true,
- incomingLink: lnwire.NewShortChanIDFromInt(1 << 55),
- outgoingLink: lnwire.NewShortChanIDFromInt(1 << 61),
- expectedErr: nil,
- },
- {
- name: "not circular, both confirmed SCID",
- aliasMapping: false,
- allowCircular: false,
- incomingLink: lnwire.NewShortChanIDFromInt(1 << 60),
- outgoingLink: lnwire.NewShortChanIDFromInt(1 << 61),
- expectedErr: nil,
- },
- }
- for _, test := range tests {
- test := test
- t.Run(test.name, func(t *testing.T) {
- t.Parallel()
- s, err := initSwitchWithTempDB(t, testStartingHeight)
- require.NoError(t, err)
- err = s.Start()
- require.NoError(t, err)
- defer func() { _ = s.Stop() }()
- if test.aliasMapping {
- // Make the incoming and outgoing point to the
- // same base SCID.
- inScid := test.incomingLink
- outScid := test.outgoingLink
- s.indexMtx.Lock()
- s.baseIndex[inScid] = outScid
- s.baseIndex[outScid] = outScid
- s.indexMtx.Unlock()
- }
- // Check for a circular forward, the hash passed can
- // be nil because it is only used for logging.
- err = s.checkCircularForward(
- test.incomingLink, test.outgoingLink,
- test.allowCircular, lntypes.Hash{},
- )
- if !reflect.DeepEqual(err, test.expectedErr) {
- t.Fatalf("expected: %v, got: %v",
- test.expectedErr, err)
- }
- })
- }
- }
- // TestSkipIneligibleLinksMultiHopForward tests that if a multi-hop HTLC comes
- // along, then we won't attempt to forward it down al ink that isn't yet able
- // to forward any HTLC's.
- func TestSkipIneligibleLinksMultiHopForward(t *testing.T) {
- tests := []multiHopFwdTest{
- // None of the channels is eligible.
- {
- name: "not eligible",
- expectedReply: lnwire.CodeUnknownNextPeer,
- },
- // Channel one has a policy failure and the other channel isn't
- // available.
- {
- name: "policy fail",
- eligible1: true,
- failure1: NewLinkError(
- lnwire.NewFinalIncorrectCltvExpiry(0),
- ),
- expectedReply: lnwire.CodeFinalIncorrectCltvExpiry,
- },
- // The requested channel is not eligible, but the packet is
- // forwarded through the other channel.
- {
- name: "non-strict success",
- eligible2: true,
- expectedReply: lnwire.CodeNone,
- },
- // The requested channel has insufficient bandwidth and the
- // other channel's policy isn't satisfied.
- {
- name: "non-strict policy fail",
- eligible1: true,
- failure1: NewDetailedLinkError(
- lnwire.NewTemporaryChannelFailure(nil),
- OutgoingFailureInsufficientBalance,
- ),
- eligible2: true,
- failure2: NewLinkError(
- lnwire.NewFinalIncorrectCltvExpiry(0),
- ),
- expectedReply: lnwire.CodeTemporaryChannelFailure,
- },
- }
- for _, test := range tests {
- test := test
- t.Run(test.name, func(t *testing.T) {
- testSkipIneligibleLinksMultiHopForward(t, &test)
- })
- }
- }
- // testSkipIneligibleLinksMultiHopForward tests that if a multi-hop HTLC comes
- // along, then we won't attempt to forward it down al ink that isn't yet able
- // to forward any HTLC's.
- func testSkipIneligibleLinksMultiHopForward(t *testing.T,
- testCase *multiHopFwdTest) {
- t.Parallel()
- var packet *htlcPacket
- alicePeer, err := newMockServer(
- t, "alice", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err, "unable to create alice server")
- bobPeer, err := newMockServer(
- t, "bob", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err, "unable to create bob server")
- s, err := initSwitchWithTempDB(t, testStartingHeight)
- require.NoError(t, err, "unable to init switch")
- if err := s.Start(); err != nil {
- t.Fatalf("unable to start switch: %v", err)
- }
- defer s.Stop()
- chanID1, aliceChanID := genID()
- aliceChannelLink := newMockChannelLink(
- s, chanID1, aliceChanID, emptyScid, alicePeer, true, false,
- false, false,
- )
- // We'll create a link for Bob, but mark the link as unable to forward
- // any new outgoing HTLC's.
- chanID2, bobChanID2 := genID()
- bobChannelLink1 := newMockChannelLink(
- s, chanID2, bobChanID2, emptyScid, bobPeer, testCase.eligible1,
- false, false, false,
- )
- bobChannelLink1.checkHtlcForwardResult = testCase.failure1
- chanID3, bobChanID3 := genID()
- bobChannelLink2 := newMockChannelLink(
- s, chanID3, bobChanID3, emptyScid, bobPeer, testCase.eligible2,
- false, false, false,
- )
- bobChannelLink2.checkHtlcForwardResult = testCase.failure2
- if err := s.AddLink(aliceChannelLink); err != nil {
- t.Fatalf("unable to add alice link: %v", err)
- }
- if err := s.AddLink(bobChannelLink1); err != nil {
- t.Fatalf("unable to add bob link: %v", err)
- }
- if err := s.AddLink(bobChannelLink2); err != nil {
- t.Fatalf("unable to add bob link: %v", err)
- }
- // Create a new packet that's destined for Bob as an incoming HTLC from
- // Alice.
- preimage := [sha256.Size]byte{1}
- rhash := sha256.Sum256(preimage[:])
- obfuscator := NewMockObfuscator()
- packet = &htlcPacket{
- incomingChanID: aliceChannelLink.ShortChanID(),
- incomingHTLCID: 0,
- outgoingChanID: bobChannelLink1.ShortChanID(),
- htlc: &lnwire.UpdateAddHTLC{
- PaymentHash: rhash,
- Amount: 1,
- },
- obfuscator: obfuscator,
- }
- // The request to forward should fail as
- if err := s.ForwardPackets(nil, packet); err != nil {
- t.Fatal(err)
- }
- // We select from all links and extract the error if exists.
- // The packet must be selected but we don't always expect a link error.
- var linkError *LinkError
- select {
- case p := <-aliceChannelLink.packets:
- linkError = p.linkFailure
- case p := <-bobChannelLink1.packets:
- linkError = p.linkFailure
- case p := <-bobChannelLink2.packets:
- linkError = p.linkFailure
- case <-time.After(time.Second):
- t.Fatal("no timely reply from switch")
- }
- failure := obfuscator.(*mockObfuscator).failure
- if testCase.expectedReply == lnwire.CodeNone {
- if linkError != nil {
- t.Fatalf("forwarding should have succeeded")
- }
- if failure != nil {
- t.Fatalf("unexpected failure %T", failure)
- }
- } else {
- if linkError == nil {
- t.Fatalf("forwarding should have failed due to " +
- "inactive link")
- }
- if failure.Code() != testCase.expectedReply {
- t.Fatalf("unexpected failure %T", failure)
- }
- }
- if s.circuits.NumOpen() != 0 {
- t.Fatal("wrong amount of circuits")
- }
- }
- // TestSkipIneligibleLinksLocalForward ensures that the switch will not attempt
- // to forward any HTLC's down a link that isn't yet eligible for forwarding.
- func TestSkipIneligibleLinksLocalForward(t *testing.T) {
- t.Parallel()
- testSkipLinkLocalForward(t, false, nil)
- }
- // TestSkipPolicyUnsatisfiedLinkLocalForward ensures that the switch will not
- // attempt to send locally initiated HTLCs that would violate the channel policy
- // down a link.
- func TestSkipPolicyUnsatisfiedLinkLocalForward(t *testing.T) {
- t.Parallel()
- testSkipLinkLocalForward(t, true, lnwire.NewTemporaryChannelFailure(nil))
- }
- func testSkipLinkLocalForward(t *testing.T, eligible bool,
- policyResult lnwire.FailureMessage) {
- // We'll create a single link for this test, marking it as being unable
- // to forward form the get go.
- alicePeer, err := newMockServer(
- t, "alice", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err, "unable to create alice server")
- s, err := initSwitchWithTempDB(t, testStartingHeight)
- require.NoError(t, err, "unable to init switch")
- if err := s.Start(); err != nil {
- t.Fatalf("unable to start switch: %v", err)
- }
- defer s.Stop()
- chanID1, _, aliceChanID, _ := genIDs()
- aliceChannelLink := newMockChannelLink(
- s, chanID1, aliceChanID, emptyScid, alicePeer, eligible, false,
- false, false,
- )
- aliceChannelLink.checkHtlcTransitResult = NewLinkError(
- policyResult,
- )
- if err := s.AddLink(aliceChannelLink); err != nil {
- t.Fatalf("unable to add alice link: %v", err)
- }
- preimage, err := genPreimage()
- require.NoError(t, err, "unable to generate preimage")
- rhash := sha256.Sum256(preimage[:])
- addMsg := &lnwire.UpdateAddHTLC{
- PaymentHash: rhash,
- Amount: 1,
- }
- // We'll attempt to send out a new HTLC that has Alice as the first
- // outgoing link. This should fail as Alice isn't yet able to forward
- // any active HTLC's.
- err = s.SendHTLC(aliceChannelLink.ShortChanID(), 0, addMsg)
- if err == nil {
- t.Fatalf("local forward should fail due to inactive link")
- }
- if s.circuits.NumOpen() != 0 {
- t.Fatal("wrong amount of circuits")
- }
- }
- // TestSwitchCancel checks that if htlc was rejected we remove unused
- // circuits.
- func TestSwitchCancel(t *testing.T) {
- t.Parallel()
- alicePeer, err := newMockServer(
- t, "alice", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err, "unable to create alice server")
- bobPeer, err := newMockServer(
- t, "bob", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err, "unable to create bob server")
- s, err := initSwitchWithTempDB(t, testStartingHeight)
- require.NoError(t, err, "unable to init switch")
- if err := s.Start(); err != nil {
- t.Fatalf("unable to start switch: %v", err)
- }
- defer s.Stop()
- chanID1, chanID2, aliceChanID, bobChanID := genIDs()
- aliceChannelLink := newMockChannelLink(
- s, chanID1, aliceChanID, emptyScid, alicePeer, true, false,
- false, false,
- )
- bobChannelLink := newMockChannelLink(
- s, chanID2, bobChanID, emptyScid, bobPeer, true, false, false,
- false,
- )
- if err := s.AddLink(aliceChannelLink); err != nil {
- t.Fatalf("unable to add alice link: %v", err)
- }
- if err := s.AddLink(bobChannelLink); err != nil {
- t.Fatalf("unable to add bob link: %v", err)
- }
- // Create request which should be forwarder from alice channel link
- // to bob channel link.
- preimage, err := genPreimage()
- require.NoError(t, err, "unable to generate preimage")
- rhash := sha256.Sum256(preimage[:])
- request := &htlcPacket{
- incomingChanID: aliceChannelLink.ShortChanID(),
- incomingHTLCID: 0,
- outgoingChanID: bobChannelLink.ShortChanID(),
- obfuscator: NewMockObfuscator(),
- htlc: &lnwire.UpdateAddHTLC{
- PaymentHash: rhash,
- Amount: 1,
- },
- }
- // Handle the request and checks that bob channel link received it.
- if err := s.ForwardPackets(nil, request); err != nil {
- t.Fatal(err)
- }
- select {
- case packet := <-bobChannelLink.packets:
- if err := bobChannelLink.completeCircuit(packet); err != nil {
- t.Fatalf("unable to complete payment circuit: %v", err)
- }
- case <-time.After(time.Second):
- t.Fatal("request was not propagated to destination")
- }
- if s.circuits.NumPending() != 1 {
- t.Fatalf("wrong amount of half circuits")
- }
- if s.circuits.NumOpen() != 1 {
- t.Fatal("wrong amount of circuits")
- }
- // Create settle request pretending that bob channel link handled
- // the add htlc request and sent the htlc settle request back. This
- // request should be forwarder back to alice channel link.
- request = &htlcPacket{
- outgoingChanID: bobChannelLink.ShortChanID(),
- outgoingHTLCID: 0,
- amount: 1,
- htlc: &lnwire.UpdateFailHTLC{},
- }
- // Handle the request and checks that payment circuit works properly.
- if err := s.ForwardPackets(nil, request); err != nil {
- t.Fatal(err)
- }
- select {
- case pkt := <-aliceChannelLink.packets:
- if err := aliceChannelLink.completeCircuit(pkt); err != nil {
- t.Fatalf("unable to remove circuit: %v", err)
- }
- case <-time.After(time.Second):
- t.Fatal("request was not propagated to channelPoint")
- }
- if s.circuits.NumPending() != 0 {
- t.Fatal("wrong amount of circuits")
- }
- if s.circuits.NumOpen() != 0 {
- t.Fatal("wrong amount of circuits")
- }
- }
- // TestSwitchAddSamePayment tests that we send the payment with the same
- // payment hash.
- func TestSwitchAddSamePayment(t *testing.T) {
- t.Parallel()
- chanID1, chanID2, aliceChanID, bobChanID := genIDs()
- alicePeer, err := newMockServer(
- t, "alice", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err, "unable to create alice server")
- bobPeer, err := newMockServer(
- t, "bob", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err, "unable to create bob server")
- s, err := initSwitchWithTempDB(t, testStartingHeight)
- require.NoError(t, err, "unable to init switch")
- if err := s.Start(); err != nil {
- t.Fatalf("unable to start switch: %v", err)
- }
- defer s.Stop()
- aliceChannelLink := newMockChannelLink(
- s, chanID1, aliceChanID, emptyScid, alicePeer, true, false,
- false, false,
- )
- bobChannelLink := newMockChannelLink(
- s, chanID2, bobChanID, emptyScid, bobPeer, true, false, false,
- false,
- )
- if err := s.AddLink(aliceChannelLink); err != nil {
- t.Fatalf("unable to add alice link: %v", err)
- }
- if err := s.AddLink(bobChannelLink); err != nil {
- t.Fatalf("unable to add bob link: %v", err)
- }
- // Create request which should be forwarder from alice channel link
- // to bob channel link.
- preimage, err := genPreimage()
- require.NoError(t, err, "unable to generate preimage")
- rhash := sha256.Sum256(preimage[:])
- request := &htlcPacket{
- incomingChanID: aliceChannelLink.ShortChanID(),
- incomingHTLCID: 0,
- outgoingChanID: bobChannelLink.ShortChanID(),
- obfuscator: NewMockObfuscator(),
- htlc: &lnwire.UpdateAddHTLC{
- PaymentHash: rhash,
- Amount: 1,
- },
- }
- // Handle the request and checks that bob channel link received it.
- if err := s.ForwardPackets(nil, request); err != nil {
- t.Fatal(err)
- }
- select {
- case packet := <-bobChannelLink.packets:
- if err := bobChannelLink.completeCircuit(packet); err != nil {
- t.Fatalf("unable to complete payment circuit: %v", err)
- }
- case <-time.After(time.Second):
- t.Fatal("request was not propagated to destination")
- }
- if s.circuits.NumOpen() != 1 {
- t.Fatal("wrong amount of circuits")
- }
- request = &htlcPacket{
- incomingChanID: aliceChannelLink.ShortChanID(),
- incomingHTLCID: 1,
- outgoingChanID: bobChannelLink.ShortChanID(),
- obfuscator: NewMockObfuscator(),
- htlc: &lnwire.UpdateAddHTLC{
- PaymentHash: rhash,
- Amount: 1,
- },
- }
- // Handle the request and checks that bob channel link received it.
- if err := s.ForwardPackets(nil, request); err != nil {
- t.Fatal(err)
- }
- select {
- case packet := <-bobChannelLink.packets:
- if err := bobChannelLink.completeCircuit(packet); err != nil {
- t.Fatalf("unable to complete payment circuit: %v", err)
- }
- case <-time.After(time.Second):
- t.Fatal("request was not propagated to destination")
- }
- if s.circuits.NumOpen() != 2 {
- t.Fatal("wrong amount of circuits")
- }
- // Create settle request pretending that bob channel link handled
- // the add htlc request and sent the htlc settle request back. This
- // request should be forwarder back to alice channel link.
- request = &htlcPacket{
- outgoingChanID: bobChannelLink.ShortChanID(),
- outgoingHTLCID: 0,
- amount: 1,
- htlc: &lnwire.UpdateFailHTLC{},
- }
- // Handle the request and checks that payment circuit works properly.
- if err := s.ForwardPackets(nil, request); err != nil {
- t.Fatal(err)
- }
- select {
- case pkt := <-aliceChannelLink.packets:
- if err := aliceChannelLink.completeCircuit(pkt); err != nil {
- t.Fatalf("unable to remove circuit: %v", err)
- }
- case <-time.After(time.Second):
- t.Fatal("request was not propagated to channelPoint")
- }
- if s.circuits.NumOpen() != 1 {
- t.Fatal("wrong amount of circuits")
- }
- request = &htlcPacket{
- outgoingChanID: bobChannelLink.ShortChanID(),
- outgoingHTLCID: 1,
- amount: 1,
- htlc: &lnwire.UpdateFailHTLC{},
- }
- // Handle the request and checks that payment circuit works properly.
- if err := s.ForwardPackets(nil, request); err != nil {
- t.Fatal(err)
- }
- select {
- case pkt := <-aliceChannelLink.packets:
- if err := aliceChannelLink.completeCircuit(pkt); err != nil {
- t.Fatalf("unable to remove circuit: %v", err)
- }
- case <-time.After(time.Second):
- t.Fatal("request was not propagated to channelPoint")
- }
- if s.circuits.NumOpen() != 0 {
- t.Fatal("wrong amount of circuits")
- }
- }
- // TestSwitchSendPayment tests ability of htlc switch to respond to the
- // users when response is came back from channel link.
- func TestSwitchSendPayment(t *testing.T) {
- t.Parallel()
- alicePeer, err := newMockServer(
- t, "alice", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err, "unable to create alice server")
- s, err := initSwitchWithTempDB(t, testStartingHeight)
- require.NoError(t, err, "unable to init switch")
- if err := s.Start(); err != nil {
- t.Fatalf("unable to start switch: %v", err)
- }
- defer s.Stop()
- chanID1, _, aliceChanID, _ := genIDs()
- aliceChannelLink := newMockChannelLink(
- s, chanID1, aliceChanID, emptyScid, alicePeer, true, false,
- false, false,
- )
- if err := s.AddLink(aliceChannelLink); err != nil {
- t.Fatalf("unable to add link: %v", err)
- }
- // Create request which should be forwarder from alice channel link
- // to bob channel link.
- preimage, err := genPreimage()
- require.NoError(t, err, "unable to generate preimage")
- rhash := sha256.Sum256(preimage[:])
- update := &lnwire.UpdateAddHTLC{
- PaymentHash: rhash,
- Amount: 1,
- }
- paymentID := uint64(123)
- // First check that the switch will correctly respond that this payment
- // ID is unknown.
- _, err = s.GetAttemptResult(
- paymentID, rhash, newMockDeobfuscator(),
- )
- if err != ErrPaymentIDNotFound {
- t.Fatalf("expected ErrPaymentIDNotFound, got %v", err)
- }
- // Handle the request and checks that bob channel link received it.
- errChan := make(chan error)
- go func() {
- err := s.SendHTLC(
- aliceChannelLink.ShortChanID(), paymentID, update,
- )
- if err != nil {
- errChan <- err
- return
- }
- resultChan, err := s.GetAttemptResult(
- paymentID, rhash, newMockDeobfuscator(),
- )
- if err != nil {
- errChan <- err
- return
- }
- result, ok := <-resultChan
- if !ok {
- errChan <- fmt.Errorf("shutting down")
- }
- if result.Error != nil {
- errChan <- result.Error
- return
- }
- errChan <- nil
- }()
- select {
- case packet := <-aliceChannelLink.packets:
- if err := aliceChannelLink.completeCircuit(packet); err != nil {
- t.Fatalf("unable to complete payment circuit: %v", err)
- }
- case err := <-errChan:
- if err != nil {
- t.Fatalf("unable to send payment: %v", err)
- }
- case <-time.After(time.Second):
- t.Fatal("request was not propagated to destination")
- }
- if s.circuits.NumOpen() != 1 {
- t.Fatal("wrong amount of circuits")
- }
- // Create fail request pretending that bob channel link handled
- // the add htlc request with error and sent the htlc fail request
- // back. This request should be forwarded back to alice channel link.
- obfuscator := NewMockObfuscator()
- failure := lnwire.NewFailIncorrectDetails(update.Amount, 100)
- reason, err := obfuscator.EncryptFirstHop(failure)
- require.NoError(t, err, "unable obfuscate failure")
- if s.IsForwardedHTLC(aliceChannelLink.ShortChanID(), update.ID) {
- t.Fatal("htlc should be identified as not forwarded")
- }
- packet := &htlcPacket{
- outgoingChanID: aliceChannelLink.ShortChanID(),
- outgoingHTLCID: 0,
- amount: 1,
- htlc: &lnwire.UpdateFailHTLC{
- Reason: reason,
- },
- }
- if err := s.ForwardPackets(nil, packet); err != nil {
- t.Fatalf("can't forward htlc packet: %v", err)
- }
- select {
- case err := <-errChan:
- assertFailureCode(
- t, err, lnwire.CodeIncorrectOrUnknownPaymentDetails,
- )
- case <-time.After(time.Second):
- t.Fatal("err wasn't received")
- }
- }
- // TestLocalPaymentNoForwardingEvents tests that if we send a series of locally
- // initiated payments, then they aren't reflected in the forwarding log.
- func TestLocalPaymentNoForwardingEvents(t *testing.T) {
- t.Parallel()
- // First, we'll create our traditional three hop network. We'll only be
- // interacting with and asserting the state of the first end point for
- // this test.
- channels, _, err := createClusterChannels(
- t, btcutil.SatoshiPerBitcoin*3, btcutil.SatoshiPerBitcoin*5,
- )
- require.NoError(t, err, "unable to create channel")
- n := newThreeHopNetwork(t, channels.aliceToBob, channels.bobToAlice,
- channels.bobToCarol, channels.carolToBob, testStartingHeight)
- if err := n.start(); err != nil {
- t.Fatalf("unable to start three hop network: %v", err)
- }
- // We'll now craft and send a payment from Alice to Bob.
- amount := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
- htlcAmt, totalTimelock, hops := generateHops(
- amount, testStartingHeight, n.firstBobChannelLink,
- )
- // With the payment crafted, we'll send it from Alice to Bob. We'll
- // wait for Alice to receive the preimage for the payment before
- // proceeding.
- receiver := n.bobServer
- firstHop := n.firstBobChannelLink.ShortChanID()
- _, err = makePayment(
- n.aliceServer, receiver, firstHop, hops, amount, htlcAmt,
- totalTimelock,
- ).Wait(30 * time.Second)
- require.NoError(t, err, "unable to make the payment")
- // At this point, we'll forcibly stop the three hop network. Doing
- // this will cause any pending forwarding events to be flushed by the
- // various switches in the network.
- n.stop()
- // With all the switches stopped, we'll fetch Alice's mock forwarding
- // event log.
- log, ok := n.aliceServer.htlcSwitch.cfg.FwdingLog.(*mockForwardingLog)
- if !ok {
- t.Fatalf("mockForwardingLog assertion failed")
- }
- log.Lock()
- defer log.Unlock()
- // If we examine the memory of the forwarding log, then it should be
- // blank.
- if len(log.events) != 0 {
- t.Fatalf("log should have no events, instead has: %v",
- spew.Sdump(log.events))
- }
- }
- // TestMultiHopPaymentForwardingEvents tests that if we send a series of
- // multi-hop payments via Alice->Bob->Carol. Then Bob properly logs forwarding
- // events, while Alice and Carol don't.
- func TestMultiHopPaymentForwardingEvents(t *testing.T) {
- t.Parallel()
- // First, we'll create our traditional three hop network.
- channels, _, err := createClusterChannels(
- t, btcutil.SatoshiPerBitcoin*3, btcutil.SatoshiPerBitcoin*5,
- )
- require.NoError(t, err, "unable to create channel")
- n := newThreeHopNetwork(t, channels.aliceToBob, channels.bobToAlice,
- channels.bobToCarol, channels.carolToBob, testStartingHeight)
- if err := n.start(); err != nil {
- t.Fatalf("unable to start three hop network: %v", err)
- }
- // We'll make now 10 payments, of 100k satoshis each from Alice to
- // Carol via Bob.
- const numPayments = 10
- finalAmt := lnwire.NewMSatFromSatoshis(100000)
- htlcAmt, totalTimelock, hops := generateHops(
- finalAmt, testStartingHeight, n.firstBobChannelLink,
- n.carolChannelLink,
- )
- firstHop := n.firstBobChannelLink.ShortChanID()
- for i := 0; i < numPayments/2; i++ {
- _, err := makePayment(
- n.aliceServer, n.carolServer, firstHop, hops, finalAmt,
- htlcAmt, totalTimelock,
- ).Wait(30 * time.Second)
- if err != nil {
- t.Fatalf("unable to send payment: %v", err)
- }
- }
- bobLog, ok := n.bobServer.htlcSwitch.cfg.FwdingLog.(*mockForwardingLog)
- if !ok {
- t.Fatalf("mockForwardingLog assertion failed")
- }
- // After sending 5 of the payments, trigger the forwarding ticker, to
- // make sure the events are properly flushed.
- bobTicker, ok := n.bobServer.htlcSwitch.cfg.FwdEventTicker.(*ticker.Force)
- if !ok {
- t.Fatalf("mockTicker assertion failed")
- }
- // We'll trigger the ticker, and wait for the events to appear in Bob's
- // forwarding log.
- timeout := time.After(15 * time.Second)
- for {
- select {
- case bobTicker.Force <- time.Now():
- case <-time.After(1 * time.Second):
- t.Fatalf("unable to force tick")
- }
- // If all 5 events is found in Bob's log, we can break out and
- // continue the test.
- bobLog.Lock()
- if len(bobLog.events) == 5 {
- bobLog.Unlock()
- break
- }
- bobLog.Unlock()
- // Otherwise wait a little bit before checking again.
- select {
- case <-time.After(50 * time.Millisecond):
- case <-timeout:
- bobLog.Lock()
- defer bobLog.Unlock()
- t.Fatalf("expected 5 events in event log, instead "+
- "found: %v", spew.Sdump(bobLog.events))
- }
- }
- // Send the remaining payments.
- for i := numPayments / 2; i < numPayments; i++ {
- _, err := makePayment(
- n.aliceServer, n.carolServer, firstHop, hops, finalAmt,
- htlcAmt, totalTimelock,
- ).Wait(30 * time.Second)
- if err != nil {
- t.Fatalf("unable to send payment: %v", err)
- }
- }
- // With all 10 payments sent. We'll now manually stop each of the
- // switches so we can examine their end state.
- n.stop()
- // Alice and Carol shouldn't have any recorded forwarding events, as
- // they were the source and the sink for these payment flows.
- aliceLog, ok := n.aliceServer.htlcSwitch.cfg.FwdingLog.(*mockForwardingLog)
- if !ok {
- t.Fatalf("mockForwardingLog assertion failed")
- }
- aliceLog.Lock()
- defer aliceLog.Unlock()
- if len(aliceLog.events) != 0 {
- t.Fatalf("log should have no events, instead has: %v",
- spew.Sdump(aliceLog.events))
- }
- carolLog, ok := n.carolServer.htlcSwitch.cfg.FwdingLog.(*mockForwardingLog)
- if !ok {
- t.Fatalf("mockForwardingLog assertion failed")
- }
- carolLog.Lock()
- defer carolLog.Unlock()
- if len(carolLog.events) != 0 {
- t.Fatalf("log should have no events, instead has: %v",
- spew.Sdump(carolLog.events))
- }
- // Bob on the other hand, should have 10 events.
- bobLog.Lock()
- defer bobLog.Unlock()
- if len(bobLog.events) != 10 {
- t.Fatalf("log should have 10 events, instead has: %v",
- spew.Sdump(bobLog.events))
- }
- // Each of the 10 events should have had all fields set properly.
- for _, event := range bobLog.events {
- // The incoming and outgoing channels should properly be set for
- // the event.
- if event.IncomingChanID != n.aliceChannelLink.ShortChanID() {
- t.Fatalf("chan id mismatch: expected %v, got %v",
- event.IncomingChanID,
- n.aliceChannelLink.ShortChanID())
- }
- if event.OutgoingChanID != n.carolChannelLink.ShortChanID() {
- t.Fatalf("chan id mismatch: expected %v, got %v",
- event.OutgoingChanID,
- n.carolChannelLink.ShortChanID())
- }
- // Additionally, the incoming and outgoing amounts should also
- // be properly set.
- if event.AmtIn != htlcAmt {
- t.Fatalf("incoming amt mismatch: expected %v, got %v",
- event.AmtIn, htlcAmt)
- }
- if event.AmtOut != finalAmt {
- t.Fatalf("outgoing amt mismatch: expected %v, got %v",
- event.AmtOut, finalAmt)
- }
- }
- }
- // TestUpdateFailMalformedHTLCErrorConversion tests that we're able to properly
- // convert malformed HTLC errors that originate at the direct link, as well as
- // during multi-hop HTLC forwarding.
- func TestUpdateFailMalformedHTLCErrorConversion(t *testing.T) {
- t.Parallel()
- // First, we'll create our traditional three hop network.
- channels, _, err := createClusterChannels(
- t, btcutil.SatoshiPerBitcoin*3, btcutil.SatoshiPerBitcoin*5,
- )
- require.NoError(t, err, "unable to create channel")
- n := newThreeHopNetwork(
- t, channels.aliceToBob, channels.bobToAlice,
- channels.bobToCarol, channels.carolToBob, testStartingHeight,
- )
- if err := n.start(); err != nil {
- t.Fatalf("unable to start three hop network: %v", err)
- }
- assertPaymentFailure := func(t *testing.T) {
- // With the decoder modified, we'll now attempt to send a
- // payment from Alice to carol.
- finalAmt := lnwire.NewMSatFromSatoshis(100000)
- htlcAmt, totalTimelock, hops := generateHops(
- finalAmt, testStartingHeight, n.firstBobChannelLink,
- n.carolChannelLink,
- )
- firstHop := n.firstBobChannelLink.ShortChanID()
- _, err = makePayment(
- n.aliceServer, n.carolServer, firstHop, hops, finalAmt,
- htlcAmt, totalTimelock,
- ).Wait(30 * time.Second)
- // The payment should fail as Carol is unable to decode the
- // onion blob sent to her.
- if err == nil {
- t.Fatalf("unable to send payment: %v", err)
- }
- routingErr := err.(ClearTextError)
- failureMsg := routingErr.WireMessage()
- if _, ok := failureMsg.(*lnwire.FailInvalidOnionKey); !ok {
- t.Fatalf("expected onion failure instead got: %v",
- routingErr.WireMessage())
- }
- }
- t.Run("multi-hop error conversion", func(t *testing.T) {
- // Now that we have our network up, we'll modify the hop
- // iterator for the Bob <-> Carol channel to fail to decode in
- // order to simulate either a replay attack or an issue
- // decoding the onion.
- n.carolOnionDecoder.decodeFail = true
- assertPaymentFailure(t)
- })
- t.Run("direct channel error conversion", func(t *testing.T) {
- // Similar to the above test case, we'll now make the Alice <->
- // Bob link always fail to decode an onion. This differs from
- // the above test case in that there's no encryption on the
- // error at all since Alice will directly receive a
- // UpdateFailMalformedHTLC message.
- n.bobOnionDecoder.decodeFail = true
- assertPaymentFailure(t)
- })
- }
- // TestSwitchGetAttemptResult tests that the switch interacts as expected with
- // the circuit map and network result store when looking up the result of a
- // payment ID. This is important for not to lose results under concurrent
- // lookup and receiving results.
- func TestSwitchGetAttemptResult(t *testing.T) {
- t.Parallel()
- const paymentID = 123
- var preimg lntypes.Preimage
- preimg[0] = 3
- s, err := initSwitchWithTempDB(t, testStartingHeight)
- require.NoError(t, err, "unable to init switch")
- if err := s.Start(); err != nil {
- t.Fatalf("unable to start switch: %v", err)
- }
- defer s.Stop()
- lookup := make(chan *PaymentCircuit, 1)
- s.circuits = &mockCircuitMap{
- lookup: lookup,
- }
- // If the payment circuit is not found in the circuit map, the payment
- // result must be found in the store if available. Since we haven't
- // added anything to the store yet, ErrPaymentIDNotFound should be
- // returned.
- lookup <- nil
- _, err = s.GetAttemptResult(
- paymentID, lntypes.Hash{}, newMockDeobfuscator(),
- )
- if err != ErrPaymentIDNotFound {
- t.Fatalf("expected ErrPaymentIDNotFound, got %v", err)
- }
- // Next let the lookup find the circuit in the circuit map. It should
- // subscribe to payment results, and return the result when available.
- lookup <- &PaymentCircuit{}
- resultChan, err := s.GetAttemptResult(
- paymentID, lntypes.Hash{}, newMockDeobfuscator(),
- )
- require.NoError(t, err, "unable to get payment result")
- // Add the result to the store.
- n := &networkResult{
- msg: &lnwire.UpdateFulfillHTLC{
- PaymentPreimage: preimg,
- },
- unencrypted: true,
- isResolution: true,
- }
- err = s.networkResults.storeResult(paymentID, n)
- require.NoError(t, err, "unable to store result")
- // The result should be available.
- select {
- case res, ok := <-resultChan:
- if !ok {
- t.Fatalf("channel was closed")
- }
- if res.Error != nil {
- t.Fatalf("got unexpected error result")
- }
- if res.Preimage != preimg {
- t.Fatalf("expected preimg %v, got %v",
- preimg, res.Preimage)
- }
- case <-time.After(1 * time.Second):
- t.Fatalf("result not received")
- }
- // As a final test, try to get the result again. Now that is no longer
- // in the circuit map, it should be immediately available from the
- // store.
- lookup <- nil
- resultChan, err = s.GetAttemptResult(
- paymentID, lntypes.Hash{}, newMockDeobfuscator(),
- )
- require.NoError(t, err, "unable to get payment result")
- select {
- case res, ok := <-resultChan:
- if !ok {
- t.Fatalf("channel was closed")
- }
- if res.Error != nil {
- t.Fatalf("got unexpected error result")
- }
- if res.Preimage != preimg {
- t.Fatalf("expected preimg %v, got %v",
- preimg, res.Preimage)
- }
- case <-time.After(1 * time.Second):
- t.Fatalf("result not received")
- }
- }
- // TestInvalidFailure tests that the switch returns an unreadable failure error
- // if the failure cannot be decrypted.
- func TestInvalidFailure(t *testing.T) {
- t.Parallel()
- alicePeer, err := newMockServer(
- t, "alice", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err, "unable to create alice server")
- s, err := initSwitchWithTempDB(t, testStartingHeight)
- require.NoError(t, err, "unable to init switch")
- if err := s.Start(); err != nil {
- t.Fatalf("unable to start switch: %v", err)
- }
- defer s.Stop()
- chanID1, _, aliceChanID, _ := genIDs()
- // Set up a mock channel link.
- aliceChannelLink := newMockChannelLink(
- s, chanID1, aliceChanID, emptyScid, alicePeer, true, false,
- false, false,
- )
- if err := s.AddLink(aliceChannelLink); err != nil {
- t.Fatalf("unable to add link: %v", err)
- }
- // Create a request which should be forwarded to the mock channel link.
- preimage, err := genPreimage()
- require.NoError(t, err, "unable to generate preimage")
- rhash := sha256.Sum256(preimage[:])
- update := &lnwire.UpdateAddHTLC{
- PaymentHash: rhash,
- Amount: 1,
- }
- paymentID := uint64(123)
- // Send the request.
- err = s.SendHTLC(
- aliceChannelLink.ShortChanID(), paymentID, update,
- )
- require.NoError(t, err, "unable to send payment")
- // Catch the packet and complete the circuit so that the switch is ready
- // for a response.
- select {
- case packet := <-aliceChannelLink.packets:
- if err := aliceChannelLink.completeCircuit(packet); err != nil {
- t.Fatalf("unable to complete payment circuit: %v", err)
- }
- case <-time.After(time.Second):
- t.Fatal("request was not propagated to destination")
- }
- // Send response packet with an unreadable failure message to the
- // switch. The reason failed is not relevant, because we mock the
- // decryption.
- packet := &htlcPacket{
- outgoingChanID: aliceChannelLink.ShortChanID(),
- outgoingHTLCID: 0,
- amount: 1,
- htlc: &lnwire.UpdateFailHTLC{
- Reason: []byte{1, 2, 3},
- },
- }
- if err := s.ForwardPackets(nil, packet); err != nil {
- t.Fatalf("can't forward htlc packet: %v", err)
- }
- // Get payment result from switch. We expect an unreadable failure
- // message error.
- deobfuscator := SphinxErrorDecrypter{
- OnionErrorDecrypter: &mockOnionErrorDecryptor{
- err: ErrUnreadableFailureMessage,
- },
- }
- resultChan, err := s.GetAttemptResult(
- paymentID, rhash, &deobfuscator,
- )
- if err != nil {
- t.Fatal(err)
- }
- select {
- case result := <-resultChan:
- if result.Error != ErrUnreadableFailureMessage {
- t.Fatal("expected unreadable failure message")
- }
- case <-time.After(time.Second):
- t.Fatal("err wasn't received")
- }
- // Modify the decryption to simulate that decryption went alright, but
- // the failure cannot be decoded.
- deobfuscator = SphinxErrorDecrypter{
- OnionErrorDecrypter: &mockOnionErrorDecryptor{
- sourceIdx: 2,
- message: []byte{200},
- },
- }
- resultChan, err = s.GetAttemptResult(
- paymentID, rhash, &deobfuscator,
- )
- if err != nil {
- t.Fatal(err)
- }
- select {
- case result := <-resultChan:
- rtErr, ok := result.Error.(ClearTextError)
- if !ok {
- t.Fatal("expected ClearTextError")
- }
- source, ok := rtErr.(*ForwardingError)
- if !ok {
- t.Fatalf("expected forwarding error, got: %T", rtErr)
- }
- if source.FailureSourceIdx != 2 {
- t.Fatal("unexpected error source index")
- }
- if rtErr.WireMessage() != nil {
- t.Fatal("expected empty failure message")
- }
- case <-time.After(time.Second):
- t.Fatal("err wasn't received")
- }
- }
- // htlcNotifierEvents is a function that generates a set of expected htlc
- // notifier evetns for each node in a three hop network with the dynamic
- // values provided. These functions take dynamic values so that changes to
- // external systems (such as our default timelock delta) do not break
- // these tests.
- type htlcNotifierEvents func(channels *clusterChannels, htlcID uint64,
- ts time.Time, htlc *lnwire.UpdateAddHTLC,
- hops []*hop.Payload,
- preimage *lntypes.Preimage) ([]interface{}, []interface{}, []interface{})
- // TestHtlcNotifier tests the notifying of htlc events that are routed over a
- // three hop network. It sets up an Alice -> Bob -> Carol network and routes
- // payments from Alice -> Carol to test events from the perspective of a
- // sending (Alice), forwarding (Bob) and receiving (Carol) node. Test cases
- // are present for saduccessful and failed payments.
- func TestHtlcNotifier(t *testing.T) {
- tests := []struct {
- name string
- // Options is a set of options to apply to the three hop
- // network's servers.
- options []serverOption
- // expectedEvents is a function which returns an expected set
- // of events for the test.
- expectedEvents htlcNotifierEvents
- // iterations is the number of times we will send a payment,
- // this is used to send more than one payment to force non-
- // zero htlc indexes to make sure we aren't just checking
- // default values.
- iterations int
- }{
- {
- name: "successful three hop payment",
- options: nil,
- expectedEvents: func(channels *clusterChannels,
- htlcID uint64, ts time.Time,
- htlc *lnwire.UpdateAddHTLC,
- hops []*hop.Payload,
- preimage *lntypes.Preimage) ([]interface{},
- []interface{}, []interface{}) {
- return getThreeHopEvents(
- channels, htlcID, ts, htlc, hops, nil, preimage,
- )
- },
- iterations: 2,
- },
- {
- name: "failed at forwarding link",
- // Set a functional option which disables bob as a
- // forwarding node to force a payment error.
- options: []serverOption{
- serverOptionRejectHtlc(false, true, false),
- },
- expectedEvents: func(channels *clusterChannels,
- htlcID uint64, ts time.Time,
- htlc *lnwire.UpdateAddHTLC,
- hops []*hop.Payload,
- preimage *lntypes.Preimage) ([]interface{},
- []interface{}, []interface{}) {
- return getThreeHopEvents(
- channels, htlcID, ts, htlc, hops,
- &LinkError{
- msg: &lnwire.FailChannelDisabled{},
- FailureDetail: OutgoingFailureForwardsDisabled,
- },
- preimage,
- )
- },
- iterations: 1,
- },
- }
- for _, test := range tests {
- test := test
- t.Run(test.name, func(t *testing.T) {
- testHtcNotifier(
- t, test.options, test.iterations,
- test.expectedEvents,
- )
- })
- }
- }
- // testHtcNotifier runs a htlc notifier test.
- func testHtcNotifier(t *testing.T, testOpts []serverOption, iterations int,
- getEvents htlcNotifierEvents) {
- t.Parallel()
- // First, we'll create our traditional three hop
- // network.
- channels, _, err := createClusterChannels(
- t, btcutil.SatoshiPerBitcoin*3, btcutil.SatoshiPerBitcoin*5,
- )
- require.NoError(t, err, "unable to create channel")
- // Mock time so that all events are reported with a static timestamp.
- now := time.Now()
- mockTime := func() time.Time {
- return now
- }
- // Create htlc notifiers for each server in the three hop network and
- // start them.
- aliceNotifier := NewHtlcNotifier(mockTime)
- if err := aliceNotifier.Start(); err != nil {
- t.Fatalf("could not start alice notifier")
- }
- t.Cleanup(func() {
- if err := aliceNotifier.Stop(); err != nil {
- t.Fatalf("failed to stop alice notifier: %v", err)
- }
- })
- bobNotifier := NewHtlcNotifier(mockTime)
- if err := bobNotifier.Start(); err != nil {
- t.Fatalf("could not start bob notifier")
- }
- t.Cleanup(func() {
- if err := bobNotifier.Stop(); err != nil {
- t.Fatalf("failed to stop bob notifier: %v", err)
- }
- })
- carolNotifier := NewHtlcNotifier(mockTime)
- if err := carolNotifier.Start(); err != nil {
- t.Fatalf("could not start carol notifier")
- }
- t.Cleanup(func() {
- if err := carolNotifier.Stop(); err != nil {
- t.Fatalf("failed to stop carol notifier: %v", err)
- }
- })
- // Create a notifier server option which will set our htlc notifiers
- // for the three hop network.
- notifierOption := serverOptionWithHtlcNotifier(
- aliceNotifier, bobNotifier, carolNotifier,
- )
- // Add the htlcNotifier option to any other options
- // set in the test.
- options := append(testOpts, notifierOption) // nolint:gocritic
- n := newThreeHopNetwork(
- t, channels.aliceToBob,
- channels.bobToAlice, channels.bobToCarol,
- channels.carolToBob, testStartingHeight,
- options...,
- )
- if err := n.start(); err != nil {
- t.Fatalf("unable to start three hop "+
- "network: %v", err)
- }
- t.Cleanup(n.stop)
- // Before we forward anything, subscribe to htlc events
- // from each notifier.
- aliceEvents, err := aliceNotifier.SubscribeHtlcEvents()
- if err != nil {
- t.Fatalf("could not subscribe to alice's"+
- " events: %v", err)
- }
- t.Cleanup(aliceEvents.Cancel)
- bobEvents, err := bobNotifier.SubscribeHtlcEvents()
- if err != nil {
- t.Fatalf("could not subscribe to bob's"+
- " events: %v", err)
- }
- t.Cleanup(bobEvents.Cancel)
- carolEvents, err := carolNotifier.SubscribeHtlcEvents()
- if err != nil {
- t.Fatalf("could not subscribe to carol's"+
- " events: %v", err)
- }
- t.Cleanup(carolEvents.Cancel)
- // Send multiple payments, as specified by the test to test incrementing
- // of htlc ids.
- for i := 0; i < iterations; i++ {
- // We'll start off by making a payment from
- // Alice -> Bob -> Carol. The preimage, generated
- // by Carol's Invoice is expected in the Settle events
- htlc, hops, preimage := n.sendThreeHopPayment(t)
- alice, bob, carol := getEvents(
- channels, uint64(i), now, htlc, hops, preimage,
- )
- checkHtlcEvents(t, aliceEvents.Updates(), alice)
- checkHtlcEvents(t, bobEvents.Updates(), bob)
- checkHtlcEvents(t, carolEvents.Updates(), carol)
- }
- }
- // checkHtlcEvents checks that a subscription has the set of htlc events
- // we expect it to have.
- func checkHtlcEvents(t *testing.T, events <-chan interface{},
- expectedEvents []interface{}) {
- t.Helper()
- for _, expected := range expectedEvents {
- select {
- case event := <-events:
- if !reflect.DeepEqual(event, expected) {
- t.Fatalf("expected %v, got: %v", expected,
- event)
- }
- case <-time.After(5 * time.Second):
- t.Fatalf("expected event: %v", expected)
- }
- }
- // Check that there are no unexpected events following.
- select {
- case event := <-events:
- t.Fatalf("unexpected event: %v", event)
- default:
- }
- }
- // sendThreeHopPayment is a helper function which sends a payment over
- // Alice -> Bob -> Carol in a three hop network and returns Alice's first htlc
- // and the remainder of the hops.
- func (n *threeHopNetwork) sendThreeHopPayment(t *testing.T) (*lnwire.UpdateAddHTLC,
- []*hop.Payload, *lntypes.Preimage) {
- amount := lnwire.NewMSatFromSatoshis(btcutil.SatoshiPerBitcoin)
- htlcAmt, totalTimelock, hops := generateHops(amount, testStartingHeight,
- n.firstBobChannelLink, n.carolChannelLink)
- blob, err := generateRoute(hops...)
- if err != nil {
- t.Fatal(err)
- }
- invoice, htlc, pid, err := generatePayment(
- amount, htlcAmt, totalTimelock, blob,
- )
- if err != nil {
- t.Fatal(err)
- }
- err = n.carolServer.registry.AddInvoice(
- context.Background(), *invoice, htlc.PaymentHash,
- )
- require.NoError(t, err, "unable to add invoice in carol registry")
- if err := n.aliceServer.htlcSwitch.SendHTLC(
- n.firstBobChannelLink.ShortChanID(), pid, htlc,
- ); err != nil {
- t.Fatalf("could not send htlc")
- }
- return htlc, hops, invoice.Terms.PaymentPreimage
- }
- // getThreeHopEvents gets the set of htlc events that we expect for a payment
- // from Alice -> Bob -> Carol. If a non-nil link error is provided, the set
- // of events will fail on Bob's outgoing link.
- func getThreeHopEvents(channels *clusterChannels, htlcID uint64,
- ts time.Time, htlc *lnwire.UpdateAddHTLC, hops []*hop.Payload,
- linkError *LinkError,
- preimage *lntypes.Preimage) ([]interface{}, []interface{}, []interface{}) {
- aliceKey := HtlcKey{
- IncomingCircuit: zeroCircuit,
- OutgoingCircuit: models.CircuitKey{
- ChanID: channels.aliceToBob.ShortChanID(),
- HtlcID: htlcID,
- },
- }
- // Alice always needs a forwarding event because she initiates the
- // send.
- aliceEvents := []interface{}{
- &ForwardingEvent{
- HtlcKey: aliceKey,
- HtlcInfo: HtlcInfo{
- OutgoingTimeLock: htlc.Expiry,
- OutgoingAmt: htlc.Amount,
- },
- HtlcEventType: HtlcEventTypeSend,
- Timestamp: ts,
- },
- }
- bobKey := HtlcKey{
- IncomingCircuit: models.CircuitKey{
- ChanID: channels.bobToAlice.ShortChanID(),
- HtlcID: htlcID,
- },
- OutgoingCircuit: models.CircuitKey{
- ChanID: channels.bobToCarol.ShortChanID(),
- HtlcID: htlcID,
- },
- }
- bobInfo := HtlcInfo{
- IncomingTimeLock: htlc.Expiry,
- IncomingAmt: htlc.Amount,
- OutgoingTimeLock: hops[1].FwdInfo.OutgoingCTLV,
- OutgoingAmt: hops[1].FwdInfo.AmountToForward,
- }
- // If we expect the payment to fail, we add failures for alice and
- // bob, and no events for carol because the payment never reaches her.
- if linkError != nil {
- aliceEvents = append(aliceEvents,
- &ForwardingFailEvent{
- HtlcKey: aliceKey,
- HtlcEventType: HtlcEventTypeSend,
- Timestamp: ts,
- },
- )
- bobEvents := []interface{}{
- &LinkFailEvent{
- HtlcKey: bobKey,
- HtlcInfo: bobInfo,
- HtlcEventType: HtlcEventTypeForward,
- LinkError: linkError,
- Incoming: false,
- Timestamp: ts,
- },
- &FinalHtlcEvent{
- CircuitKey: bobKey.IncomingCircuit,
- Settled: false,
- Offchain: true,
- Timestamp: ts,
- },
- }
- return aliceEvents, bobEvents, nil
- }
- // If we want to get events for a successful payment, we add a settle
- // for alice, a forward and settle for bob and a receive settle for
- // carol.
- aliceEvents = append(
- aliceEvents,
- &SettleEvent{
- HtlcKey: aliceKey,
- Preimage: *preimage,
- HtlcEventType: HtlcEventTypeSend,
- Timestamp: ts,
- },
- )
- bobEvents := []interface{}{
- &ForwardingEvent{
- HtlcKey: bobKey,
- HtlcInfo: bobInfo,
- HtlcEventType: HtlcEventTypeForward,
- Timestamp: ts,
- },
- &SettleEvent{
- HtlcKey: bobKey,
- Preimage: *preimage,
- HtlcEventType: HtlcEventTypeForward,
- Timestamp: ts,
- },
- &FinalHtlcEvent{
- CircuitKey: bobKey.IncomingCircuit,
- Settled: true,
- Offchain: true,
- Timestamp: ts,
- },
- }
- carolEvents := []interface{}{
- &SettleEvent{
- HtlcKey: HtlcKey{
- IncomingCircuit: models.CircuitKey{
- ChanID: channels.carolToBob.ShortChanID(),
- HtlcID: htlcID,
- },
- OutgoingCircuit: zeroCircuit,
- },
- Preimage: *preimage,
- HtlcEventType: HtlcEventTypeReceive,
- Timestamp: ts,
- }, &FinalHtlcEvent{
- CircuitKey: models.CircuitKey{
- ChanID: channels.carolToBob.ShortChanID(),
- HtlcID: htlcID,
- },
- Settled: true,
- Offchain: true,
- Timestamp: ts,
- },
- }
- return aliceEvents, bobEvents, carolEvents
- }
- type mockForwardInterceptor struct {
- t *testing.T
- interceptedChan chan InterceptedPacket
- }
- func (m *mockForwardInterceptor) InterceptForwardHtlc(
- intercepted InterceptedPacket) error {
- m.interceptedChan <- intercepted
- return nil
- }
- func (m *mockForwardInterceptor) getIntercepted() InterceptedPacket {
- m.t.Helper()
- select {
- case p := <-m.interceptedChan:
- return p
- case <-time.After(time.Second):
- require.Fail(m.t, "timeout")
- return InterceptedPacket{}
- }
- }
- func assertNumCircuits(t *testing.T, s *Switch, pending, opened int) {
- if s.circuits.NumPending() != pending {
- t.Fatalf("wrong amount of half circuits, expected %v but "+
- "got %v", pending, s.circuits.NumPending())
- }
- if s.circuits.NumOpen() != opened {
- t.Fatalf("wrong amount of circuits, expected %v but got %v",
- opened, s.circuits.NumOpen())
- }
- }
- func assertOutgoingLinkReceive(t *testing.T, targetLink *mockChannelLink,
- expectReceive bool) *htlcPacket {
- // Pull packet from targetLink link.
- select {
- case packet := <-targetLink.packets:
- if !expectReceive {
- t.Fatal("forward was intercepted, shouldn't land at bob link")
- } else if err := targetLink.completeCircuit(packet); err != nil {
- t.Fatalf("unable to complete payment circuit: %v", err)
- }
- return packet
- case <-time.After(time.Second):
- if expectReceive {
- t.Fatal("request was not propagated to destination")
- }
- }
- return nil
- }
- func assertOutgoingLinkReceiveIntercepted(t *testing.T,
- targetLink *mockChannelLink) {
- t.Helper()
- select {
- case <-targetLink.packets:
- case <-time.After(time.Second):
- t.Fatal("request was not propagated to destination")
- }
- }
- type interceptableSwitchTestContext struct {
- t *testing.T
- preimage [sha256.Size]byte
- rhash [32]byte
- onionBlob [1366]byte
- incomingHtlcID uint64
- cltvRejectDelta uint32
- cltvInterceptDelta uint32
- forwardInterceptor *mockForwardInterceptor
- aliceChannelLink *mockChannelLink
- bobChannelLink *mockChannelLink
- s *Switch
- }
- func newInterceptableSwitchTestContext(
- t *testing.T) *interceptableSwitchTestContext {
- chanID1, chanID2, aliceChanID, bobChanID := genIDs()
- alicePeer, err := newMockServer(
- t, "alice", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err, "unable to create alice server")
- bobPeer, err := newMockServer(
- t, "bob", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err, "unable to create bob server")
- tempPath := t.TempDir()
- cdb, err := channeldb.Open(tempPath)
- require.NoError(t, err, "unable to open channeldb")
- t.Cleanup(func() { cdb.Close() })
- s, err := initSwitchWithDB(testStartingHeight, cdb)
- require.NoError(t, err, "unable to init switch")
- if err := s.Start(); err != nil {
- t.Fatalf("unable to start switch: %v", err)
- }
- aliceChannelLink := newMockChannelLink(
- s, chanID1, aliceChanID, emptyScid, alicePeer, true, false,
- false, false,
- )
- bobChannelLink := newMockChannelLink(
- s, chanID2, bobChanID, emptyScid, bobPeer, true, false, false,
- false,
- )
- if err := s.AddLink(aliceChannelLink); err != nil {
- t.Fatalf("unable to add alice link: %v", err)
- }
- if err := s.AddLink(bobChannelLink); err != nil {
- t.Fatalf("unable to add bob link: %v", err)
- }
- preimage := [sha256.Size]byte{1}
- ctx := &interceptableSwitchTestContext{
- t: t,
- preimage: preimage,
- rhash: sha256.Sum256(preimage[:]),
- onionBlob: [1366]byte{4, 5, 6},
- incomingHtlcID: uint64(0),
- cltvRejectDelta: 10,
- cltvInterceptDelta: 13,
- forwardInterceptor: &mockForwardInterceptor{
- t: t,
- interceptedChan: make(chan InterceptedPacket),
- },
- aliceChannelLink: aliceChannelLink,
- bobChannelLink: bobChannelLink,
- s: s,
- }
- return ctx
- }
- func (c *interceptableSwitchTestContext) createTestPacket() *htlcPacket {
- c.incomingHtlcID++
- return &htlcPacket{
- incomingChanID: c.aliceChannelLink.ShortChanID(),
- incomingHTLCID: c.incomingHtlcID,
- incomingTimeout: testStartingHeight + c.cltvInterceptDelta + 1,
- outgoingChanID: c.bobChannelLink.ShortChanID(),
- obfuscator: NewMockObfuscator(),
- htlc: &lnwire.UpdateAddHTLC{
- PaymentHash: c.rhash,
- Amount: 1,
- OnionBlob: c.onionBlob,
- },
- }
- }
- func (c *interceptableSwitchTestContext) finish() {
- if err := c.s.Stop(); err != nil {
- c.t.Fatalf(err.Error())
- }
- }
- func (c *interceptableSwitchTestContext) createSettlePacket(
- outgoingHTLCID uint64) *htlcPacket {
- return &htlcPacket{
- outgoingChanID: c.bobChannelLink.ShortChanID(),
- outgoingHTLCID: outgoingHTLCID,
- amount: 1,
- htlc: &lnwire.UpdateFulfillHTLC{
- PaymentPreimage: c.preimage,
- },
- }
- }
- func TestSwitchHoldForward(t *testing.T) {
- t.Parallel()
- c := newInterceptableSwitchTestContext(t)
- defer c.finish()
- notifier := &mock.ChainNotifier{
- EpochChan: make(chan *chainntnfs.BlockEpoch, 1),
- }
- notifier.EpochChan <- &chainntnfs.BlockEpoch{Height: testStartingHeight}
- switchForwardInterceptor, err := NewInterceptableSwitch(
- &InterceptableSwitchConfig{
- Switch: c.s,
- CltvRejectDelta: c.cltvRejectDelta,
- CltvInterceptDelta: c.cltvInterceptDelta,
- Notifier: notifier,
- },
- )
- require.NoError(t, err)
- require.NoError(t, switchForwardInterceptor.Start())
- switchForwardInterceptor.SetInterceptor(c.forwardInterceptor.InterceptForwardHtlc)
- linkQuit := make(chan struct{})
- // Test a forward that expires too soon.
- packet := c.createTestPacket()
- packet.incomingTimeout = testStartingHeight + c.cltvRejectDelta - 1
- err = switchForwardInterceptor.ForwardPackets(linkQuit, false, packet)
- require.NoError(t, err, "can't forward htlc packet")
- assertOutgoingLinkReceive(t, c.bobChannelLink, false)
- assertOutgoingLinkReceiveIntercepted(t, c.aliceChannelLink)
- assertNumCircuits(t, c.s, 0, 0)
- // Test a forward that expires too soon and can't be failed.
- packet = c.createTestPacket()
- packet.incomingTimeout = testStartingHeight + c.cltvRejectDelta - 1
- // Simulate an error during the composition of the failure message.
- currentCallback := c.s.cfg.FetchLastChannelUpdate
- c.s.cfg.FetchLastChannelUpdate = func(
- lnwire.ShortChannelID) (*lnwire.ChannelUpdate, error) {
- return nil, errors.New("cannot fetch update")
- }
- err = switchForwardInterceptor.ForwardPackets(linkQuit, false, packet)
- require.NoError(t, err, "can't forward htlc packet")
- receivedPkt := assertOutgoingLinkReceive(t, c.bobChannelLink, true)
- assertNumCircuits(t, c.s, 1, 1)
- require.NoError(t, switchForwardInterceptor.ForwardPackets(
- linkQuit, false,
- c.createSettlePacket(receivedPkt.outgoingHTLCID),
- ))
- assertOutgoingLinkReceive(t, c.aliceChannelLink, true)
- assertNumCircuits(t, c.s, 0, 0)
- c.s.cfg.FetchLastChannelUpdate = currentCallback
- // Test resume a hold forward.
- assertNumCircuits(t, c.s, 0, 0)
- err = switchForwardInterceptor.ForwardPackets(
- linkQuit, false, c.createTestPacket(),
- )
- require.NoError(t, err)
- assertNumCircuits(t, c.s, 0, 0)
- assertOutgoingLinkReceive(t, c.bobChannelLink, false)
- require.NoError(t, switchForwardInterceptor.Resolve(&FwdResolution{
- Action: FwdActionResume,
- Key: c.forwardInterceptor.getIntercepted().IncomingCircuit,
- }))
- receivedPkt = assertOutgoingLinkReceive(t, c.bobChannelLink, true)
- assertNumCircuits(t, c.s, 1, 1)
- // settling the htlc to close the circuit.
- err = switchForwardInterceptor.ForwardPackets(
- linkQuit, false,
- c.createSettlePacket(receivedPkt.outgoingHTLCID),
- )
- require.NoError(t, err)
- assertOutgoingLinkReceive(t, c.aliceChannelLink, true)
- assertNumCircuits(t, c.s, 0, 0)
- // Test resume a hold forward after disconnection.
- require.NoError(t, switchForwardInterceptor.ForwardPackets(
- linkQuit, false, c.createTestPacket(),
- ))
- // Wait until the packet is offered to the interceptor.
- _ = c.forwardInterceptor.getIntercepted()
- // No forward expected yet.
- assertNumCircuits(t, c.s, 0, 0)
- assertOutgoingLinkReceive(t, c.bobChannelLink, false)
- // Disconnect should resume the forwarding.
- switchForwardInterceptor.SetInterceptor(nil)
- receivedPkt = assertOutgoingLinkReceive(t, c.bobChannelLink, true)
- assertNumCircuits(t, c.s, 1, 1)
- // Settle the htlc to close the circuit.
- require.NoError(t, switchForwardInterceptor.ForwardPackets(
- linkQuit, false,
- c.createSettlePacket(receivedPkt.outgoingHTLCID),
- ))
- assertOutgoingLinkReceive(t, c.aliceChannelLink, true)
- assertNumCircuits(t, c.s, 0, 0)
- // Test failing a hold forward
- switchForwardInterceptor.SetInterceptor(
- c.forwardInterceptor.InterceptForwardHtlc,
- )
- require.NoError(t, switchForwardInterceptor.ForwardPackets(
- linkQuit, false, c.createTestPacket(),
- ))
- assertNumCircuits(t, c.s, 0, 0)
- assertOutgoingLinkReceive(t, c.bobChannelLink, false)
- require.NoError(t, switchForwardInterceptor.Resolve(&FwdResolution{
- Action: FwdActionFail,
- Key: c.forwardInterceptor.getIntercepted().IncomingCircuit,
- FailureCode: lnwire.CodeTemporaryChannelFailure,
- }))
- assertOutgoingLinkReceive(t, c.bobChannelLink, false)
- assertOutgoingLinkReceive(t, c.aliceChannelLink, true)
- assertNumCircuits(t, c.s, 0, 0)
- // Test failing a hold forward with a failure message.
- require.NoError(t,
- switchForwardInterceptor.ForwardPackets(
- linkQuit, false, c.createTestPacket(),
- ),
- )
- assertNumCircuits(t, c.s, 0, 0)
- assertOutgoingLinkReceive(t, c.bobChannelLink, false)
- reason := lnwire.OpaqueReason([]byte{1, 2, 3})
- require.NoError(t, switchForwardInterceptor.Resolve(&FwdResolution{
- Action: FwdActionFail,
- Key: c.forwardInterceptor.getIntercepted().IncomingCircuit,
- FailureMessage: reason,
- }))
- assertOutgoingLinkReceive(t, c.bobChannelLink, false)
- packet = assertOutgoingLinkReceive(t, c.aliceChannelLink, true)
- require.Equal(t, reason, packet.htlc.(*lnwire.UpdateFailHTLC).Reason)
- assertNumCircuits(t, c.s, 0, 0)
- // Test failing a hold forward with a malformed htlc failure.
- err = switchForwardInterceptor.ForwardPackets(
- linkQuit, false, c.createTestPacket(),
- )
- require.NoError(t, err)
- assertNumCircuits(t, c.s, 0, 0)
- assertOutgoingLinkReceive(t, c.bobChannelLink, false)
- code := lnwire.CodeInvalidOnionKey
- require.NoError(t, switchForwardInterceptor.Resolve(&FwdResolution{
- Action: FwdActionFail,
- Key: c.forwardInterceptor.getIntercepted().IncomingCircuit,
- FailureCode: code,
- }))
- assertOutgoingLinkReceive(t, c.bobChannelLink, false)
- packet = assertOutgoingLinkReceive(t, c.aliceChannelLink, true)
- failPacket := packet.htlc.(*lnwire.UpdateFailHTLC)
- shaOnionBlob := sha256.Sum256(c.onionBlob[:])
- expectedFailure := &lnwire.FailInvalidOnionKey{
- OnionSHA256: shaOnionBlob,
- }
- fwdErr, err := newMockDeobfuscator().DecryptError(failPacket.Reason)
- require.NoError(t, err)
- require.Equal(t, expectedFailure, fwdErr.WireMessage())
- assertNumCircuits(t, c.s, 0, 0)
- // Test settling a hold forward
- require.NoError(t, switchForwardInterceptor.ForwardPackets(
- linkQuit, false, c.createTestPacket(),
- ))
- assertNumCircuits(t, c.s, 0, 0)
- assertOutgoingLinkReceive(t, c.bobChannelLink, false)
- require.NoError(t, switchForwardInterceptor.Resolve(&FwdResolution{
- Key: c.forwardInterceptor.getIntercepted().IncomingCircuit,
- Action: FwdActionSettle,
- Preimage: c.preimage,
- }))
- assertOutgoingLinkReceive(t, c.bobChannelLink, false)
- assertOutgoingLinkReceive(t, c.aliceChannelLink, true)
- assertNumCircuits(t, c.s, 0, 0)
- require.NoError(t, switchForwardInterceptor.Stop())
- // Test always-on interception.
- notifier = &mock.ChainNotifier{
- EpochChan: make(chan *chainntnfs.BlockEpoch, 1),
- }
- notifier.EpochChan <- &chainntnfs.BlockEpoch{Height: testStartingHeight}
- switchForwardInterceptor, err = NewInterceptableSwitch(
- &InterceptableSwitchConfig{
- Switch: c.s,
- CltvRejectDelta: c.cltvRejectDelta,
- CltvInterceptDelta: c.cltvInterceptDelta,
- RequireInterceptor: true,
- Notifier: notifier,
- },
- )
- require.NoError(t, err)
- require.NoError(t, switchForwardInterceptor.Start())
- // Forward a fresh packet. It is expected to be failed immediately,
- // because there is no interceptor registered.
- require.NoError(t, switchForwardInterceptor.ForwardPackets(
- linkQuit, false, c.createTestPacket(),
- ))
- assertOutgoingLinkReceive(t, c.bobChannelLink, false)
- assertOutgoingLinkReceive(t, c.aliceChannelLink, true)
- assertNumCircuits(t, c.s, 0, 0)
- // Forward a replayed packet. It is expected to be held until the
- // interceptor connects. To continue the test, it needs to be ran in a
- // goroutine.
- errChan := make(chan error)
- go func() {
- errChan <- switchForwardInterceptor.ForwardPackets(
- linkQuit, true, c.createTestPacket(),
- )
- }()
- // Assert that nothing is forward to the switch.
- assertOutgoingLinkReceive(t, c.bobChannelLink, false)
- assertNumCircuits(t, c.s, 0, 0)
- // Register an interceptor.
- switchForwardInterceptor.SetInterceptor(
- c.forwardInterceptor.InterceptForwardHtlc,
- )
- // Expect the ForwardPackets call to unblock.
- require.NoError(t, <-errChan)
- // Now expect the queued packet to come through.
- c.forwardInterceptor.getIntercepted()
- // Disconnect and reconnect interceptor.
- switchForwardInterceptor.SetInterceptor(nil)
- switchForwardInterceptor.SetInterceptor(
- c.forwardInterceptor.InterceptForwardHtlc,
- )
- // A replay of the held packet is expected.
- intercepted := c.forwardInterceptor.getIntercepted()
- // Settle the packet.
- require.NoError(t, switchForwardInterceptor.Resolve(&FwdResolution{
- Key: intercepted.IncomingCircuit,
- Action: FwdActionSettle,
- Preimage: c.preimage,
- }))
- assertOutgoingLinkReceive(t, c.bobChannelLink, false)
- assertOutgoingLinkReceive(t, c.aliceChannelLink, true)
- assertNumCircuits(t, c.s, 0, 0)
- require.NoError(t, switchForwardInterceptor.Stop())
- select {
- case <-c.forwardInterceptor.interceptedChan:
- require.Fail(t, "unexpected interception")
- default:
- }
- }
- func TestInterceptableSwitchWatchDog(t *testing.T) {
- t.Parallel()
- c := newInterceptableSwitchTestContext(t)
- defer c.finish()
- // Start interceptable switch.
- notifier := &mock.ChainNotifier{
- EpochChan: make(chan *chainntnfs.BlockEpoch, 1),
- }
- notifier.EpochChan <- &chainntnfs.BlockEpoch{Height: testStartingHeight}
- switchForwardInterceptor, err := NewInterceptableSwitch(
- &InterceptableSwitchConfig{
- Switch: c.s,
- CltvRejectDelta: c.cltvRejectDelta,
- CltvInterceptDelta: c.cltvInterceptDelta,
- Notifier: notifier,
- },
- )
- require.NoError(t, err)
- require.NoError(t, switchForwardInterceptor.Start())
- // Set interceptor.
- switchForwardInterceptor.SetInterceptor(
- c.forwardInterceptor.InterceptForwardHtlc,
- )
- // Receive a packet.
- linkQuit := make(chan struct{})
- packet := c.createTestPacket()
- err = switchForwardInterceptor.ForwardPackets(linkQuit, false, packet)
- require.NoError(t, err, "can't forward htlc packet")
- // Intercept the packet.
- intercepted := c.forwardInterceptor.getIntercepted()
- require.Equal(t,
- int32(packet.incomingTimeout-c.cltvRejectDelta),
- intercepted.AutoFailHeight,
- )
- // Htlc expires before a resolution from the interceptor.
- notifier.EpochChan <- &chainntnfs.BlockEpoch{
- Height: int32(packet.incomingTimeout) -
- int32(c.cltvRejectDelta),
- }
- // Expect the htlc to be failed back.
- assertOutgoingLinkReceive(t, c.aliceChannelLink, true)
- // It is too late now to resolve. Expect an error.
- require.Error(t, switchForwardInterceptor.Resolve(&FwdResolution{
- Action: FwdActionSettle,
- Key: intercepted.IncomingCircuit,
- Preimage: c.preimage,
- }))
- }
- // TestSwitchDustForwarding tests that the switch properly fails HTLC's which
- // have incoming or outgoing links that breach their dust thresholds.
- func TestSwitchDustForwarding(t *testing.T) {
- t.Parallel()
- // We'll create a three-hop network:
- // - Alice has a dust limit of 200sats with Bob
- // - Bob has a dust limit of 800sats with Alice
- // - Bob has a dust limit of 200sats with Carol
- // - Carol has a dust limit of 800sats with Bob
- channels, _, err := createClusterChannels(
- t, btcutil.SatoshiPerBitcoin, btcutil.SatoshiPerBitcoin,
- )
- require.NoError(t, err)
- n := newThreeHopNetwork(
- t, channels.aliceToBob, channels.bobToAlice,
- channels.bobToCarol, channels.carolToBob, testStartingHeight,
- )
- err = n.start()
- require.NoError(t, err)
- // We'll also put Alice and Bob into hodl.ExitSettle mode, such that
- // they won't settle incoming exit-hop HTLC's automatically.
- n.aliceChannelLink.cfg.HodlMask = hodl.ExitSettle.Mask()
- n.firstBobChannelLink.cfg.HodlMask = hodl.ExitSettle.Mask()
- // We'll test that once the default threshold is exceeded on the
- // Alice -> Bob channel, either side's calls to SendHTLC will fail.
- //
- // Alice will send 357 HTLC's of 700sats. Bob will also send 357 HTLC's
- // of 700sats. If either side attempts to send a dust HTLC, it will
- // fail so amounts below 800sats will breach the dust threshold.
- amt := lnwire.NewMSatFromSatoshis(700)
- aliceBobFirstHop := n.aliceChannelLink.ShortChanID()
- sendDustHtlcs(t, n, true, amt, aliceBobFirstHop)
- sendDustHtlcs(t, n, false, amt, aliceBobFirstHop)
- // Generate the parameters needed for Bob to send another dust HTLC.
- _, timelock, hops := generateHops(
- amt, testStartingHeight, n.aliceChannelLink,
- )
- blob, err := generateRoute(hops...)
- require.NoError(t, err)
- // Assert that if Bob sends a dust HTLC it will fail.
- failingPreimage := lntypes.Preimage{0, 0, 3}
- failingHash := failingPreimage.Hash()
- failingHtlc := &lnwire.UpdateAddHTLC{
- PaymentHash: failingHash,
- Amount: amt,
- Expiry: timelock,
- OnionBlob: blob,
- }
- checkAlmostDust := func(link *channelLink, mbox MailBox,
- remote bool) bool {
- timeout := time.After(15 * time.Second)
- pollInterval := 300 * time.Millisecond
- expectedDust := 357 * 2 * amt
- for {
- <-time.After(pollInterval)
- select {
- case <-timeout:
- return false
- default:
- }
- linkDust := link.getDustSum(remote)
- localMailDust, remoteMailDust := mbox.DustPackets()
- totalDust := linkDust
- if remote {
- totalDust += remoteMailDust
- } else {
- totalDust += localMailDust
- }
- if totalDust == expectedDust {
- break
- }
- }
- return true
- }
- // Wait until Bob is almost at the dust threshold.
- bobMbox := n.bobServer.htlcSwitch.mailOrchestrator.GetOrCreateMailBox(
- n.firstBobChannelLink.ChanID(),
- n.firstBobChannelLink.ShortChanID(),
- )
- require.True(t, checkAlmostDust(n.firstBobChannelLink, bobMbox, false))
- // Assert that the HTLC is failed due to the dust threshold.
- err = n.bobServer.htlcSwitch.SendHTLC(
- aliceBobFirstHop, uint64(357), failingHtlc,
- )
- require.ErrorIs(t, err, errDustThresholdExceeded)
- // Generate the parameters needed for bob to send a non-dust HTLC.
- nondustAmt := lnwire.NewMSatFromSatoshis(10_000)
- _, _, hops = generateHops(
- nondustAmt, testStartingHeight, n.aliceChannelLink,
- )
- blob, err = generateRoute(hops...)
- require.NoError(t, err)
- // Now attempt to send an HTLC above Bob's dust limit. It should
- // succeed.
- nondustPreimage := lntypes.Preimage{0, 0, 4}
- nondustHash := nondustPreimage.Hash()
- nondustHtlc := &lnwire.UpdateAddHTLC{
- PaymentHash: nondustHash,
- Amount: nondustAmt,
- Expiry: timelock,
- OnionBlob: blob,
- }
- // Assert that SendHTLC succeeds and evaluateDustThreshold returns
- // false.
- err = n.bobServer.htlcSwitch.SendHTLC(
- aliceBobFirstHop, uint64(358), nondustHtlc,
- )
- require.NoError(t, err)
- // Introduce Carol into the mix and assert that sending a multi-hop
- // dust HTLC to Alice will fail. Bob should fail back the HTLC with a
- // temporary channel failure.
- carolAmt, carolTimelock, carolHops := generateHops(
- amt, testStartingHeight, n.secondBobChannelLink,
- n.aliceChannelLink,
- )
- carolBlob, err := generateRoute(carolHops...)
- require.NoError(t, err)
- carolPreimage := lntypes.Preimage{0, 0, 5}
- carolHash := carolPreimage.Hash()
- carolHtlc := &lnwire.UpdateAddHTLC{
- PaymentHash: carolHash,
- Amount: carolAmt,
- Expiry: carolTimelock,
- OnionBlob: carolBlob,
- }
- // Initialize Carol's attempt ID.
- carolAttemptID := 0
- err = n.carolServer.htlcSwitch.SendHTLC(
- n.carolChannelLink.ShortChanID(), uint64(carolAttemptID),
- carolHtlc,
- )
- require.NoError(t, err)
- carolAttemptID++
- carolResultChan, err := n.carolServer.htlcSwitch.GetAttemptResult(
- uint64(carolAttemptID-1), carolHash, newMockDeobfuscator(),
- )
- require.NoError(t, err)
- result, ok := <-carolResultChan
- require.True(t, ok)
- assertFailureCode(
- t, result.Error, lnwire.CodeTemporaryChannelFailure,
- )
- // Send an HTLC from Alice to Carol and assert that it is failed at the
- // call to SendHTLC.
- htlcAmt, totalTimelock, aliceHops := generateHops(
- amt, testStartingHeight, n.firstBobChannelLink,
- n.carolChannelLink,
- )
- blob, err = generateRoute(aliceHops...)
- require.NoError(t, err)
- aliceMultihopPreimage := lntypes.Preimage{0, 0, 6}
- aliceMultihopHash := aliceMultihopPreimage.Hash()
- aliceMultihopHtlc := &lnwire.UpdateAddHTLC{
- PaymentHash: aliceMultihopHash,
- Amount: htlcAmt,
- Expiry: totalTimelock,
- OnionBlob: blob,
- }
- // Wait until Alice's expected dust for the remote commitment is just
- // under the dust threshold.
- aliceOrch := n.aliceServer.htlcSwitch.mailOrchestrator
- aliceMbox := aliceOrch.GetOrCreateMailBox(
- n.aliceChannelLink.ChanID(), n.aliceChannelLink.ShortChanID(),
- )
- require.True(t, checkAlmostDust(n.aliceChannelLink, aliceMbox, true))
- err = n.aliceServer.htlcSwitch.SendHTLC(
- n.aliceChannelLink.ShortChanID(), uint64(357),
- aliceMultihopHtlc,
- )
- require.ErrorIs(t, err, errDustThresholdExceeded)
- }
- // sendDustHtlcs is a helper function used to send many dust HTLC's to test the
- // Switch's dust-threshold logic. It takes a boolean denoting whether or not
- // Alice is the sender.
- func sendDustHtlcs(t *testing.T, n *threeHopNetwork, alice bool,
- amt lnwire.MilliSatoshi, sid lnwire.ShortChannelID) {
- t.Helper()
- // The number of dust HTLC's we'll send for both Alice and Bob.
- numHTLCs := 357
- // Extract the destination into a variable. If alice is the sender, the
- // destination is Bob.
- destLink := n.aliceChannelLink
- if alice {
- destLink = n.firstBobChannelLink
- }
- // Create hops that will be used in the onion payload.
- htlcAmt, totalTimelock, hops := generateHops(
- amt, testStartingHeight, destLink,
- )
- // Convert the hops to a blob that will be put in the Add message.
- blob, err := generateRoute(hops...)
- require.NoError(t, err)
- // Create a slice to store the preimages.
- preimages := make([]lntypes.Preimage, numHTLCs)
- // Initialize the attempt ID used in SendHTLC calls.
- attemptID := uint64(0)
- // Deterministically generate preimages. Avoid the all-zeroes preimage
- // because that will be rejected by the database. We'll use a different
- // third byte for Alice and Bob.
- endByte := byte(2)
- if alice {
- endByte = byte(3)
- }
- for i := 0; i < numHTLCs; i++ {
- preimages[i] = lntypes.Preimage{byte(i >> 8), byte(i), endByte}
- }
- sendingSwitch := n.bobServer.htlcSwitch
- if alice {
- sendingSwitch = n.aliceServer.htlcSwitch
- }
- // Call SendHTLC in a loop for numHTLCs.
- for i := 0; i < numHTLCs; i++ {
- // Construct the htlc packet.
- hash := preimages[i].Hash()
- htlc := &lnwire.UpdateAddHTLC{
- PaymentHash: hash,
- Amount: htlcAmt,
- Expiry: totalTimelock,
- OnionBlob: blob,
- }
- for {
- // It may be the case that the dust threshold is hit
- // before all 357*2 HTLC's are sent due to double
- // counting. Get around this by continuing to send
- // until successful.
- err = sendingSwitch.SendHTLC(sid, attemptID, htlc)
- if err == nil {
- break
- }
- }
- attemptID++
- }
- }
- // TestSwitchMailboxDust tests that the switch takes into account the mailbox
- // dust when evaluating the dust threshold. The mockChannelLink does not have
- // channel state, so this only tests the switch-mailbox interaction.
- func TestSwitchMailboxDust(t *testing.T) {
- t.Parallel()
- alicePeer, err := newMockServer(
- t, "alice", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err)
- bobPeer, err := newMockServer(
- t, "bob", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err)
- carolPeer, err := newMockServer(
- t, "carol", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err)
- s, err := initSwitchWithTempDB(t, testStartingHeight)
- require.NoError(t, err)
- err = s.Start()
- require.NoError(t, err)
- defer func() {
- _ = s.Stop()
- }()
- chanID1, chanID2, aliceChanID, bobChanID := genIDs()
- chanID3, carolChanID := genID()
- aliceLink := newMockChannelLink(
- s, chanID1, aliceChanID, emptyScid, alicePeer, true, false,
- false, false,
- )
- err = s.AddLink(aliceLink)
- require.NoError(t, err)
- bobLink := newMockChannelLink(
- s, chanID2, bobChanID, emptyScid, bobPeer, true, false, false,
- false,
- )
- err = s.AddLink(bobLink)
- require.NoError(t, err)
- carolLink := newMockChannelLink(
- s, chanID3, carolChanID, emptyScid, carolPeer, true, false,
- false, false,
- )
- err = s.AddLink(carolLink)
- require.NoError(t, err)
- // mockChannelLink sets the local and remote dust limits of the mailbox
- // to 400 satoshis and the feerate to 0. We'll fill the mailbox up with
- // dust packets and assert that calls to SendHTLC will fail.
- preimage, err := genPreimage()
- require.NoError(t, err)
- rhash := sha256.Sum256(preimage[:])
- amt := lnwire.NewMSatFromSatoshis(350)
- addMsg := &lnwire.UpdateAddHTLC{
- PaymentHash: rhash,
- Amount: amt,
- ChanID: chanID1,
- }
- // Initialize the carolHTLCID.
- var carolHTLCID uint64
- // It will take aliceCount HTLC's of 350sats to fill up Alice's mailbox
- // to the point where another would put Alice over the dust threshold.
- aliceCount := 1428
- mailbox := s.mailOrchestrator.GetOrCreateMailBox(chanID1, aliceChanID)
- for i := 0; i < aliceCount; i++ {
- alicePkt := &htlcPacket{
- incomingChanID: carolChanID,
- incomingHTLCID: carolHTLCID,
- outgoingChanID: aliceChanID,
- obfuscator: NewMockObfuscator(),
- incomingAmount: amt,
- amount: amt,
- htlc: addMsg,
- }
- err = mailbox.AddPacket(alicePkt)
- require.NoError(t, err)
- carolHTLCID++
- }
- // Sending one more HTLC to Alice should result in the dust threshold
- // being breached.
- err = s.SendHTLC(aliceChanID, 0, addMsg)
- require.ErrorIs(t, err, errDustThresholdExceeded)
- // We'll now call ForwardPackets from Bob to ensure that the mailbox
- // sum is also accounted for in the forwarding case.
- packet := &htlcPacket{
- incomingChanID: bobChanID,
- incomingHTLCID: 0,
- outgoingChanID: aliceChanID,
- obfuscator: NewMockObfuscator(),
- incomingAmount: amt,
- amount: amt,
- htlc: &lnwire.UpdateAddHTLC{
- PaymentHash: rhash,
- Amount: amt,
- ChanID: chanID1,
- },
- }
- err = s.ForwardPackets(nil, packet)
- require.NoError(t, err)
- // Bob should receive a failure from the switch.
- select {
- case p := <-bobLink.packets:
- require.NotEmpty(t, p.linkFailure)
- assertFailureCode(
- t, p.linkFailure, lnwire.CodeTemporaryChannelFailure,
- )
- case <-time.After(5 * time.Second):
- t.Fatal("no timely reply from switch")
- }
- }
- // TestSwitchResolution checks the ability of the switch to persist and handle
- // resolution messages.
- func TestSwitchResolution(t *testing.T) {
- t.Parallel()
- alicePeer, err := newMockServer(
- t, "alice", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err)
- bobPeer, err := newMockServer(
- t, "bob", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err)
- s, err := initSwitchWithTempDB(t, testStartingHeight)
- require.NoError(t, err)
- // Even though we intend to Stop s later in the test, it is safe to
- // defer this Stop since its execution it is protected by an atomic
- // guard, guaranteeing it executes at most once.
- t.Cleanup(func() { var _ = s.Stop() })
- err = s.Start()
- require.NoError(t, err)
- chanID1, chanID2, aliceChanID, bobChanID := genIDs()
- aliceChannelLink := newMockChannelLink(
- s, chanID1, aliceChanID, emptyScid, alicePeer, true, false,
- false, false,
- )
- bobChannelLink := newMockChannelLink(
- s, chanID2, bobChanID, emptyScid, bobPeer, true, false, false,
- false,
- )
- err = s.AddLink(aliceChannelLink)
- require.NoError(t, err)
- err = s.AddLink(bobChannelLink)
- require.NoError(t, err)
- // Create an add htlcPacket that Alice will send to Bob.
- preimage, err := genPreimage()
- require.NoError(t, err)
- rhash := sha256.Sum256(preimage[:])
- packet := &htlcPacket{
- incomingChanID: aliceChannelLink.ShortChanID(),
- incomingHTLCID: 0,
- outgoingChanID: bobChannelLink.ShortChanID(),
- obfuscator: NewMockObfuscator(),
- htlc: &lnwire.UpdateAddHTLC{
- PaymentHash: rhash,
- Amount: 1,
- },
- }
- err = s.ForwardPackets(nil, packet)
- require.NoError(t, err)
- // Bob will receive the packet and open the circuit.
- select {
- case <-bobChannelLink.packets:
- err = bobChannelLink.completeCircuit(packet)
- require.NoError(t, err)
- case <-time.After(time.Second):
- t.Fatal("request was not propagated to destination")
- }
- // Check that only one circuit is open.
- require.Equal(t, 1, s.circuits.NumOpen())
- // We'll send a settle resolution to Switch that should go to Alice.
- settleResMsg := contractcourt.ResolutionMsg{
- SourceChan: bobChanID,
- HtlcIndex: 0,
- PreImage: &preimage,
- }
- // Before the resolution is sent, remove alice's link so we can assert
- // that the resolution is actually stored. Otherwise, it would be
- // deleted shortly after being sent.
- s.RemoveLink(chanID1)
- // Send the resolution message.
- err = s.ProcessContractResolution(settleResMsg)
- require.NoError(t, err)
- // Assert that the resolution store contains the settle reoslution.
- resMsgs, err := s.resMsgStore.fetchAllResolutionMsg()
- require.NoError(t, err)
- require.Equal(t, 1, len(resMsgs))
- require.Equal(t, settleResMsg.SourceChan, resMsgs[0].SourceChan)
- require.Equal(t, settleResMsg.HtlcIndex, resMsgs[0].HtlcIndex)
- require.Nil(t, resMsgs[0].Failure)
- require.Equal(t, preimage, *resMsgs[0].PreImage)
- // Now we'll restart Alice's link and delete the circuit.
- err = s.AddLink(aliceChannelLink)
- require.NoError(t, err)
- // Alice will receive the packet and open the circuit.
- select {
- case alicePkt := <-aliceChannelLink.packets:
- err = aliceChannelLink.completeCircuit(alicePkt)
- require.NoError(t, err)
- case <-time.After(time.Second):
- t.Fatal("request was not propagated to destination")
- }
- // Assert that there are no more circuits.
- require.Equal(t, 0, s.circuits.NumOpen())
- // We'll restart the Switch and assert that Alice does not receive
- // another packet.
- switchDB := s.cfg.DB.(*channeldb.DB)
- err = s.Stop()
- require.NoError(t, err)
- s, err = initSwitchWithDB(testStartingHeight, switchDB)
- require.NoError(t, err)
- err = s.Start()
- require.NoError(t, err)
- defer func() {
- _ = s.Stop()
- }()
- err = s.AddLink(aliceChannelLink)
- require.NoError(t, err)
- err = s.AddLink(bobChannelLink)
- require.NoError(t, err)
- // Alice should not receive a packet since the Switch should have
- // deleted the resolution message since the circuit was closed.
- select {
- case alicePkt := <-aliceChannelLink.packets:
- t.Fatalf("received erroneous packet: %v", alicePkt)
- case <-time.After(time.Second * 5):
- }
- // Check that the resolution message no longer exists in the store.
- resMsgs, err = s.resMsgStore.fetchAllResolutionMsg()
- require.NoError(t, err)
- require.Equal(t, 0, len(resMsgs))
- }
- // TestSwitchForwardFailAlias tests that if ForwardPackets returns a failure
- // before actually forwarding, the ChannelUpdate uses the SCID from the
- // incoming channel and does not leak private information like the UTXO.
- func TestSwitchForwardFailAlias(t *testing.T) {
- tests := []struct {
- name string
- // Whether or not Alice will be a zero-conf channel or an
- // option-scid-alias channel (feature-bit).
- zeroConf bool
- }{
- {
- name: "option-scid-alias forwarding failure",
- zeroConf: false,
- },
- {
- name: "zero-conf forwarding failure",
- zeroConf: true,
- },
- }
- for _, test := range tests {
- test := test
- t.Run(test.name, func(t *testing.T) {
- testSwitchForwardFailAlias(t, test.zeroConf)
- })
- }
- }
- func testSwitchForwardFailAlias(t *testing.T, zeroConf bool) {
- t.Parallel()
- chanID1, chanID2, aliceChanID, bobChanID := genIDs()
- alicePeer, err := newMockServer(
- t, "alice", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err)
- bobPeer, err := newMockServer(
- t, "bob", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err)
- tempPath := t.TempDir()
- cdb, err := channeldb.Open(tempPath)
- require.NoError(t, err)
- t.Cleanup(func() { cdb.Close() })
- s, err := initSwitchWithDB(testStartingHeight, cdb)
- require.NoError(t, err)
- err = s.Start()
- require.NoError(t, err)
- // Make Alice's channel zero-conf or option-scid-alias (feature bit).
- aliceAlias := lnwire.ShortChannelID{
- BlockHeight: 16_000_000,
- TxIndex: 5,
- TxPosition: 5,
- }
- var aliceLink *mockChannelLink
- if zeroConf {
- aliceLink = newMockChannelLink(
- s, chanID1, aliceAlias, aliceChanID, alicePeer, true,
- true, true, false,
- )
- } else {
- aliceLink = newMockChannelLink(
- s, chanID1, aliceChanID, emptyScid, alicePeer, true,
- true, false, true,
- )
- aliceLink.addAlias(aliceAlias)
- }
- err = s.AddLink(aliceLink)
- require.NoError(t, err)
- bobLink := newMockChannelLink(
- s, chanID2, bobChanID, emptyScid, bobPeer, true, false, false,
- false,
- )
- err = s.AddLink(bobLink)
- require.NoError(t, err)
- // Create a packet that will be sent from Alice to Bob via the switch.
- preimage := [sha256.Size]byte{1}
- rhash := sha256.Sum256(preimage[:])
- ogPacket := &htlcPacket{
- incomingChanID: aliceLink.ShortChanID(),
- incomingHTLCID: 0,
- outgoingChanID: bobLink.ShortChanID(),
- obfuscator: NewMockObfuscator(),
- htlc: &lnwire.UpdateAddHTLC{
- PaymentHash: rhash,
- Amount: 1,
- },
- }
- // Forward the packet and check that Bob's channel link received it.
- err = s.ForwardPackets(nil, ogPacket)
- require.NoError(t, err)
- // Assert that the circuits are in the expected state.
- require.Equal(t, 1, s.circuits.NumPending())
- require.Equal(t, 0, s.circuits.NumOpen())
- // Pull packet from Bob's link, and do nothing with it.
- select {
- case <-bobLink.packets:
- case <-s.quit:
- t.Fatal("switch shutting down, failed to forward packet")
- }
- // Now we will restart the Switch to trigger the LoadedFromDisk logic.
- err = s.Stop()
- require.NoError(t, err)
- err = cdb.Close()
- require.NoError(t, err)
- cdb2, err := channeldb.Open(tempPath)
- require.NoError(t, err)
- t.Cleanup(func() { cdb2.Close() })
- s2, err := initSwitchWithDB(testStartingHeight, cdb2)
- require.NoError(t, err)
- err = s2.Start()
- require.NoError(t, err)
- defer func() {
- _ = s2.Stop()
- }()
- var aliceLink2 *mockChannelLink
- if zeroConf {
- aliceLink2 = newMockChannelLink(
- s2, chanID1, aliceAlias, aliceChanID, alicePeer, true,
- true, true, false,
- )
- } else {
- aliceLink2 = newMockChannelLink(
- s2, chanID1, aliceChanID, emptyScid, alicePeer, true,
- true, false, true,
- )
- aliceLink2.addAlias(aliceAlias)
- }
- err = s2.AddLink(aliceLink2)
- require.NoError(t, err)
- bobLink2 := newMockChannelLink(
- s2, chanID2, bobChanID, emptyScid, bobPeer, true, false, false,
- false,
- )
- err = s2.AddLink(bobLink2)
- require.NoError(t, err)
- // Reforward the ogPacket and wait for Alice to receive a failure
- // packet.
- err = s2.ForwardPackets(nil, ogPacket)
- require.NoError(t, err)
- select {
- case failPacket := <-aliceLink2.packets:
- // Assert that the failPacket does not leak UTXO information.
- // This means checking that aliceChanID was not returned.
- msg := failPacket.linkFailure.msg
- failMsg, ok := msg.(*lnwire.FailTemporaryChannelFailure)
- require.True(t, ok)
- require.Equal(t, aliceAlias, failMsg.Update.ShortChannelID)
- case <-s2.quit:
- t.Fatal("switch shutting down, failed to forward packet")
- }
- }
- // TestSwitchAliasFailAdd tests that the mailbox does not leak UTXO information
- // when failing back an HTLC due to the 5-second timeout. This is tested in the
- // switch rather than the mailbox because the mailbox tests do not have the
- // proper context (e.g. the Switch's failAliasUpdate function). The caveat here
- // is that if the private UTXO is already known, it is fine to send a failure
- // back. This tests option-scid-alias (feature-bit) and zero-conf channels.
- func TestSwitchAliasFailAdd(t *testing.T) {
- tests := []struct {
- name string
- // Denotes whether the opened channel will be zero-conf.
- zeroConf bool
- // Denotes whether the opened channel will be private.
- private bool
- // Denotes whether an alias was used during forwarding.
- useAlias bool
- }{
- {
- name: "public zero-conf using alias",
- zeroConf: true,
- private: false,
- useAlias: true,
- },
- {
- name: "public zero-conf using real",
- zeroConf: true,
- private: false,
- useAlias: true,
- },
- {
- name: "private zero-conf using alias",
- zeroConf: true,
- private: true,
- useAlias: true,
- },
- {
- name: "public option-scid-alias using alias",
- zeroConf: false,
- private: false,
- useAlias: true,
- },
- {
- name: "public option-scid-alias using real",
- zeroConf: false,
- private: false,
- useAlias: false,
- },
- {
- name: "private option-scid-alias using alias",
- zeroConf: false,
- private: true,
- useAlias: true,
- },
- }
- for _, test := range tests {
- test := test
- t.Run(test.name, func(t *testing.T) {
- testSwitchAliasFailAdd(
- t, test.zeroConf, test.private, test.useAlias,
- )
- })
- }
- }
- func testSwitchAliasFailAdd(t *testing.T, zeroConf, private, useAlias bool) {
- t.Parallel()
- chanID1, chanID2, aliceChanID, bobChanID := genIDs()
- alicePeer, err := newMockServer(
- t, "alice", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err)
- bobPeer, err := newMockServer(
- t, "bob", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err)
- tempPath := t.TempDir()
- cdb, err := channeldb.Open(tempPath)
- require.NoError(t, err)
- defer cdb.Close()
- s, err := initSwitchWithDB(testStartingHeight, cdb)
- require.NoError(t, err)
- // Change the mailOrchestrator's expiry to a second.
- s.mailOrchestrator.cfg.expiry = time.Second
- err = s.Start()
- require.NoError(t, err)
- defer func() {
- _ = s.Stop()
- }()
- // Make Alice's channel zero-conf or option-scid-alias (feature bit).
- aliceAlias := lnwire.ShortChannelID{
- BlockHeight: 16_000_000,
- TxIndex: 5,
- TxPosition: 5,
- }
- aliceAlias2 := aliceAlias
- aliceAlias2.TxPosition = 6
- var aliceLink *mockChannelLink
- if zeroConf {
- aliceLink = newMockChannelLink(
- s, chanID1, aliceAlias, aliceChanID, alicePeer, true,
- private, true, false,
- )
- aliceLink.addAlias(aliceAlias2)
- } else {
- aliceLink = newMockChannelLink(
- s, chanID1, aliceChanID, emptyScid, alicePeer, true,
- private, false, true,
- )
- aliceLink.addAlias(aliceAlias)
- aliceLink.addAlias(aliceAlias2)
- }
- err = s.AddLink(aliceLink)
- require.NoError(t, err)
- bobLink := newMockChannelLink(
- s, chanID2, bobChanID, emptyScid, bobPeer, true, true, false,
- false,
- )
- err = s.AddLink(bobLink)
- require.NoError(t, err)
- // Create a packet that Bob will send to Alice via ForwardPackets.
- preimage := [sha256.Size]byte{1}
- rhash := sha256.Sum256(preimage[:])
- ogPacket := &htlcPacket{
- incomingChanID: bobLink.ShortChanID(),
- incomingHTLCID: 0,
- obfuscator: NewMockObfuscator(),
- htlc: &lnwire.UpdateAddHTLC{
- PaymentHash: rhash,
- Amount: 1,
- },
- }
- // Determine which outgoingChanID to set based on the useAlias boolean.
- outgoingChanID := aliceChanID
- if useAlias {
- // Choose randomly from the 2 possible aliases.
- aliases := aliceLink.getAliases()
- idx := mrand.Intn(len(aliases))
- outgoingChanID = aliases[idx]
- }
- ogPacket.outgoingChanID = outgoingChanID
- // Forward the packet so Alice's mailbox fails it backwards.
- err = s.ForwardPackets(nil, ogPacket)
- require.NoError(t, err)
- // Assert that the circuits are in the expected state.
- require.Equal(t, 1, s.circuits.NumPending())
- require.Equal(t, 0, s.circuits.NumOpen())
- // Wait to receive the packet from Bob's mailbox.
- select {
- case failPacket := <-bobLink.packets:
- // Assert that failPacket returns the expected SCID in the
- // ChannelUpdate.
- msg := failPacket.linkFailure.msg
- failMsg, ok := msg.(*lnwire.FailTemporaryChannelFailure)
- require.True(t, ok)
- require.Equal(t, outgoingChanID, failMsg.Update.ShortChannelID)
- case <-s.quit:
- t.Fatal("switch shutting down, failed to receive fail packet")
- }
- }
- // TestSwitchHandlePacketForwardAlias checks that handlePacketForward (which
- // calls CheckHtlcForward) does not leak the UTXO in a failure message for
- // alias channels. This test requires us to have a REAL link, which we also
- // must modify in order to test it properly (e.g. making it a private channel).
- // This doesn't lead to good code, but short of refactoring the link-generation
- // code there is not a good alternative.
- func TestSwitchHandlePacketForward(t *testing.T) {
- tests := []struct {
- name string
- // Denotes whether or not the channel will be zero-conf.
- zeroConf bool
- // Denotes whether or not the channel will have negotiated the
- // option-scid-alias feature-bit and is not zero-conf.
- optionFeature bool
- // Denotes whether or not the channel will be private.
- private bool
- // Denotes whether or not the alias will be used for
- // forwarding.
- useAlias bool
- }{
- {
- name: "public zero-conf using alias",
- zeroConf: true,
- private: false,
- useAlias: true,
- },
- {
- name: "public zero-conf using real",
- zeroConf: true,
- private: false,
- useAlias: false,
- },
- {
- name: "private zero-conf using alias",
- zeroConf: true,
- private: true,
- useAlias: true,
- },
- {
- name: "public option-scid-alias using alias",
- zeroConf: false,
- optionFeature: true,
- private: false,
- useAlias: true,
- },
- {
- name: "public option-scid-alias using real",
- zeroConf: false,
- optionFeature: true,
- private: false,
- useAlias: false,
- },
- {
- name: "private option-scid-alias using alias",
- zeroConf: false,
- optionFeature: true,
- private: true,
- useAlias: true,
- },
- }
- for _, test := range tests {
- test := test
- t.Run(test.name, func(t *testing.T) {
- testSwitchHandlePacketForward(
- t, test.zeroConf, test.private, test.useAlias,
- test.optionFeature,
- )
- })
- }
- }
- func testSwitchHandlePacketForward(t *testing.T, zeroConf, private,
- useAlias, optionFeature bool) {
- t.Parallel()
- // Create a link for Alice that we'll add to the switch.
- harness, err :=
- newSingleLinkTestHarness(t, btcutil.SatoshiPerBitcoin, 0)
- require.NoError(t, err)
- aliceLink := harness.aliceLink
- s, err := initSwitchWithTempDB(t, testStartingHeight)
- if err != nil {
- t.Fatalf("unable to init switch: %v", err)
- }
- if err := s.Start(); err != nil {
- t.Fatalf("unable to start switch: %v", err)
- }
- defer func() {
- _ = s.Stop()
- }()
- // Change Alice's ShortChanID and OtherShortChanID here.
- aliceAlias := lnwire.ShortChannelID{
- BlockHeight: 16_000_000,
- TxIndex: 5,
- TxPosition: 5,
- }
- aliceAlias2 := aliceAlias
- aliceAlias2.TxPosition = 6
- aliceChannelLink := aliceLink.(*channelLink)
- aliceChannelState := aliceChannelLink.channel.State()
- // Set the link's GetAliases function.
- aliceChannelLink.cfg.GetAliases = func(
- base lnwire.ShortChannelID) []lnwire.ShortChannelID {
- return []lnwire.ShortChannelID{aliceAlias, aliceAlias2}
- }
- if !private {
- // Change the channel to public depending on the test.
- aliceChannelState.ChannelFlags = lnwire.FFAnnounceChannel
- }
- // If this is an option-scid-alias feature-bit non-zero-conf channel,
- // we'll mark the channel as such.
- if optionFeature {
- aliceChannelState.ChanType |= channeldb.ScidAliasFeatureBit
- }
- // This is the ShortChannelID field in the OpenChannel struct.
- aliceScid := aliceLink.ShortChanID()
- if zeroConf {
- // Store the alias in the shortChanID field and mark the real
- // scid in the database.
- err = aliceChannelState.MarkRealScid(aliceScid)
- require.NoError(t, err)
- aliceChannelState.ChanType |= channeldb.ZeroConfBit
- }
- err = s.AddLink(aliceLink)
- require.NoError(t, err)
- // Add a mockChannelLink for Bob.
- bobChanID, bobScid := genID()
- bobPeer, err := newMockServer(
- t, "bob", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err)
- bobLink := newMockChannelLink(
- s, bobChanID, bobScid, emptyScid, bobPeer, true, false, false,
- false,
- )
- err = s.AddLink(bobLink)
- require.NoError(t, err)
- preimage := [sha256.Size]byte{1}
- rhash := sha256.Sum256(preimage[:])
- ogPacket := &htlcPacket{
- incomingChanID: bobLink.ShortChanID(),
- incomingHTLCID: 0,
- incomingAmount: 1000,
- obfuscator: NewMockObfuscator(),
- htlc: &lnwire.UpdateAddHTLC{
- PaymentHash: rhash,
- Amount: 1,
- },
- }
- // Determine which outgoingChanID to set based on the useAlias bool.
- outgoingChanID := aliceScid
- if useAlias {
- // Choose from the possible aliases.
- aliases := aliceLink.getAliases()
- idx := mrand.Intn(len(aliases))
- outgoingChanID = aliases[idx]
- }
- ogPacket.outgoingChanID = outgoingChanID
- // Forward the packet to Alice and she should fail it back with an
- // AmountBelowMinimum FailureMessage.
- err = s.ForwardPackets(nil, ogPacket)
- require.NoError(t, err)
- select {
- case failPacket := <-bobLink.packets:
- // Assert that failPacket returns the expected ChannelUpdate.
- msg := failPacket.linkFailure.msg
- failMsg, ok := msg.(*lnwire.FailAmountBelowMinimum)
- require.True(t, ok)
- require.Equal(t, outgoingChanID, failMsg.Update.ShortChannelID)
- case <-s.quit:
- t.Fatal("switch shutting down, failed to receive failure")
- }
- }
- // TestSwitchAliasInterceptFail tests that when the InterceptableSwitch fails
- // an incoming HTLC, it does not leak the on-chain UTXO for option-scid-alias
- // (feature bit) or zero-conf channels.
- func TestSwitchAliasInterceptFail(t *testing.T) {
- tests := []struct {
- name string
- // Denotes whether or not the incoming channel is a zero-conf
- // channel or an option-scid-alias channel instead (feature
- // bit).
- zeroConf bool
- }{
- {
- name: "option-scid-alias",
- zeroConf: false,
- },
- {
- name: "zero-conf",
- zeroConf: true,
- },
- }
- for _, test := range tests {
- test := test
- t.Run(test.name, func(t *testing.T) {
- testSwitchAliasInterceptFail(t, test.zeroConf)
- })
- }
- }
- func testSwitchAliasInterceptFail(t *testing.T, zeroConf bool) {
- t.Parallel()
- chanID, aliceScid := genID()
- alicePeer, err := newMockServer(
- t, "alice", testStartingHeight, nil, testDefaultDelta,
- )
- require.NoError(t, err)
- tempPath := t.TempDir()
- cdb, err := channeldb.Open(tempPath)
- require.NoError(t, err)
- t.Cleanup(func() { cdb.Close() })
- s, err := initSwitchWithDB(testStartingHeight, cdb)
- require.NoError(t, err)
- err = s.Start()
- require.NoError(t, err)
- defer func() {
- _ = s.Stop()
- }()
- // Make Alice's alias here.
- aliceAlias := lnwire.ShortChannelID{
- BlockHeight: 16_000_000,
- TxIndex: 5,
- TxPosition: 5,
- }
- aliceAlias2 := aliceAlias
- aliceAlias2.TxPosition = 6
- var aliceLink *mockChannelLink
- if zeroConf {
- aliceLink = newMockChannelLink(
- s, chanID, aliceAlias, aliceScid, alicePeer, true,
- true, true, false,
- )
- aliceLink.addAlias(aliceAlias2)
- } else {
- aliceLink = newMockChannelLink(
- s, chanID, aliceScid, emptyScid, alicePeer, true,
- true, false, true,
- )
- aliceLink.addAlias(aliceAlias)
- aliceLink.addAlias(aliceAlias2)
- }
- err = s.AddLink(aliceLink)
- require.NoError(t, err)
- // Now we'll create the packet that will be sent from the Alice link.
- preimage := [sha256.Size]byte{1}
- rhash := sha256.Sum256(preimage[:])
- ogPacket := &htlcPacket{
- incomingChanID: aliceLink.ShortChanID(),
- incomingTimeout: 1000,
- incomingHTLCID: 0,
- outgoingChanID: lnwire.ShortChannelID{},
- obfuscator: NewMockObfuscator(),
- htlc: &lnwire.UpdateAddHTLC{
- PaymentHash: rhash,
- Amount: 1,
- },
- }
- // Now setup the interceptable switch so that we can reject this
- // packet.
- forwardInterceptor := &mockForwardInterceptor{
- t: t,
- interceptedChan: make(chan InterceptedPacket),
- }
- notifier := &mock.ChainNotifier{
- EpochChan: make(chan *chainntnfs.BlockEpoch, 1),
- }
- notifier.EpochChan <- &chainntnfs.BlockEpoch{Height: testStartingHeight}
- interceptSwitch, err := NewInterceptableSwitch(
- &InterceptableSwitchConfig{
- Switch: s,
- Notifier: notifier,
- CltvRejectDelta: 10,
- CltvInterceptDelta: 13,
- },
- )
- require.NoError(t, err)
- require.NoError(t, interceptSwitch.Start())
- interceptSwitch.SetInterceptor(forwardInterceptor.InterceptForwardHtlc)
- err = interceptSwitch.ForwardPackets(nil, false, ogPacket)
- require.NoError(t, err)
- inCircuit := forwardInterceptor.getIntercepted().IncomingCircuit
- require.NoError(t, interceptSwitch.resolve(&FwdResolution{
- Action: FwdActionFail,
- Key: inCircuit,
- FailureCode: lnwire.CodeTemporaryChannelFailure,
- }))
- select {
- case failPacket := <-aliceLink.packets:
- // Assert that failPacket returns the expected ChannelUpdate.
- failHtlc, ok := failPacket.htlc.(*lnwire.UpdateFailHTLC)
- require.True(t, ok)
- fwdErr, err := newMockDeobfuscator().DecryptError(
- failHtlc.Reason,
- )
- require.NoError(t, err)
- failure := fwdErr.WireMessage()
- failureMsg, ok := failure.(*lnwire.FailTemporaryChannelFailure)
- require.True(t, ok)
- failScid := failureMsg.Update.ShortChannelID
- isAlias := failScid == aliceAlias || failScid == aliceAlias2
- require.True(t, isAlias)
- case <-s.quit:
- t.Fatalf("switch shutting down, failed to receive failure")
- }
- require.NoError(t, interceptSwitch.Stop())
- }
|