Skip to content
Charles Shao
Go back

供应链透明度(下):OpenRTB schain 用运行时逐跳记录做供应链核验

views

上篇《ads.txt / app-ads.txt / sellers.json》 讲透了三份静态声明——媒体用 ads.txt/app-ads.txt 声明「谁被授权卖我」,中介用 sellers.json 声明「系统内每个账号背后是谁」。但静态声明只回答「谁被授权」,回答不了「一条具体的请求实际经过了哪些中介、顺序如何」。补上后半问的,是 OpenRTB 的 SupplyChain 对象(schain)——本篇的主角。

三份静态声明是事先公布的授权表,schain 则是随每条 bid request 逐跳生长的运单:每个中介转发请求前,都须将自身作为一个 node 追加进去,记录「我是谁、从谁手中接手」。买方收到请求后,以运单上的每一跳比对授权表,即可判定这条链是否完整、每一手是否被授权——这正是把供应链从「可声明」推进到「可核验」的最后一环。本篇同时收入买方侧的完整工程:先离线将三份声明抓取、清洗、编译成索引,再在竞价热路径上以 schain 逐跳核验。

SupplyChain 对象结构:一个紫色面板标题 Bid Request · source.schain(OpenRTB 2.6),内部自上而下三个卡片——顶部 schain 头部 { ver: "1.0", complete: 1 };nodes[0] 最上游·直连媒体的 SSP(asi: ssp-a.com、sid: 540123、hp: 1);nodes[1] 转售交易所(asi: adx-b.com、sid: 8842、hp: 1);卡片之间用向下箭头表示顺序;底部旁注说明每个中介转发请求前必须把自己作为一个 node 追加进 schain(有序、nodes[0] 最靠近媒体),complete=1 表示整条链从媒体到发出请求的交易所完整无缺、hp=1 表示该节点参与本次结算,node 的 asi 对应 sellers.json 域名、sid 对应其 seller_id,由此把运行时路径接回前两张静态声明 schain 是一条「谁经手了谁」的运行时账。nodes[0] 最靠近媒体,每多一跳中介就 append 一个 node;complete=1 是完整性承诺。每个 node 的 asi/sid 正是 sellers.json 和 ads.txt 的对账键——运行时记录在这里接回上篇的静态声明。

TL;DR

Table of contents

Open Table of contents

一、schain 是什么:运行时逐跳记录

上篇的 ads.txt/sellers.json 都是静态声明:媒体和中介各自「事先」贴出的授权表。但一条具体的 bid request 到底实际经过了哪些中介、顺序如何?这要靠 OpenRTB 里的 SupplyChain 对象(schain)——它随请求逐跳生长,记录真实路径。

schain 挂在 source.schain(OpenRTB 2.6;早期实现放在 source.ext.schain),核心是一个有序的 node 数组。node 与顶层字段:

字段含义
asi广告系统标识(该中介的域名)——对应 sellers.json 发布域名、ads.txt 的广告系统域名
sid卖方在该系统里的账号 ID——对应 sellers.json 的 seller_id、ads.txt 的账号 ID
hp该 node 是否参与本次结算(handles payment),通常为 1
rid / name / domain请求 ID、可选的名称和域名
complete(顶层)1=链完整(从媒体到发出请求的交易所每一跳都在);0=不完整
ver(顶层)schain 版本

asisid 正是上篇反复强调的对账键:asi 是广告系统域名(据此即可拼出 https://{asi}/sellers.json),sid 是卖方在该系统内的账号编号。schain 的每个 node 即「在某个 asi 内、由某个 sid 经手的这一跳」——运行时记录采用的正是静态声明的同一套键,两边才能对齐。

几条工程规则:

这恰好回应上篇留下的库存洗钱难题:洗钱的本质是「买方看不到经了几手、每一手是否被授权」。schain 将「经了几手」变成一个公开的有序数组——若想隐去中间跳,就须不把自己加入,complete 便只能标 0(暴露异常);若如实全部加入,那几手转售则完全暴露在买方眼前(进入下文的逐跳核验)。两条路都被堵死。

二、把声明和运行时接上:买方交叉核验闭环

单看每个标准都只是「一份表」;真正产生价值的,是买方将它们交叉核验的那一刻。核验逻辑是一条决策链:

买方核验决策树:从一条 bid request 出发,自上而下四个判断,每个判断「是」向下继续、「否」向右指向一个失败结论——① schain.complete==1(链是否完整)?否→链不完整/被截断:降权或直接拒(琥珀);② node 的 asi.sellers.json 里存在该 sid?否→未知卖方 unauthorized:拒绝出价(红);③ 卖方域名的 ads.txt/app-ads.txt 授权了 (asi,sid)?否→域名伪造/非法转售:丢弃(红);④ 路径是否无冗余跳数(SPO 视角)?否→可信但路径冗长:SPO 降低优先级(琥珀);四步全部「是」落到底部绿色结论:全部通过→可信供应路径,正常竞价并据 schain 跳数做 SPO 选最短路;底部旁注给出域名伪造实例(请求声称 nytimes.com 但其 ads.txt 未列出该 (asi,sid) 即判伪造丢弃)并强调核验是尽力而为、对不完整或对不上的路径多用降权而非硬拒以免误杀真实库存 核验是一条「层层收紧」的漏斗:先看链完不完整(schain.complete),再看每一跳的卖方在不在中介的 sellers.json 里,再回查卖方域名的 ads.txt 授没授权这一跳;全过才是可信路径,还能顺带按跳数做 SPO。任一步不过,对应到「降权」或「丢弃」。

一条 bid request 的完整核验,就是对它的 schain 走一遍:

  1. 完整性schain.complete == 1?不完整说明有隐藏的中间跳——保守买方直接拒,多数买方降权。
  2. 卖方存在性:对每个 node,去 asi 的 sellers.json(离线建好的 sellersMap,见第三节)里查 sid 是否存在。查不到 = 未知卖方(unauthorized),拒。
  3. 授权一致性:从 sellers.json 拿到该卖方的 domain,回查该 domain 的 ads.txt/app-ads.txt(离线建好的 authMap)——里面是否有 (asi, sid) 这条授权记录?没有 = 域名伪造或非法转售,丢弃。
  4. 路径质量(SPO):全部通过后,链是否有冗余跳数?这不是「真伪」问题而是「效率」问题——买方据此优先走更短、更少抽成的路径(见第五节)。

域名伪造这一典型欺诈验证闭环:一条请求声称库存来自 nytimes.com,schain 中带有某个 (asi, sid)。买方回查 nytimes.com/ads.txt——发现其中并无这条 (asi, sid) 的授权 → 判定伪造、丢弃。伪造者可篡改 bid request 中的字段,却改不了 nytimes.com 根目录下那份由纽约时报自身控制的 ads.txt。信任锚点从「上游自报」转移到「媒体自身掌控的公开文件」——这正是整套设计的支点。

再以上篇那条库存洗钱链(X → SSP-A → ADX-B → SSP-C)验证一遍:若 X 试图隐去自己这一跳,schain 中便少一个 node、complete 应为 0,第①步即被拦下;若如实记录,则走到第③步,回查 nytimes.com/ads.txt 会发现并无授权 X 对应的 (asi, sid)——未授权,丢弃。无论隐去与否,洗钱链都通不过这个闭环。

一句话本质:四件套单独均可被绕过或造假,但交叉核验将它们相互锁定——伪造者须同时篡改「媒体的 ads.txt + 中介的 sellers.json + 请求的 schain」并使三者自洽,成本陡增至不划算。

三、买方侧工程(离线):全网抓取、解析、建索引

核验逻辑清楚了,但真正的工程量在于把这三份文件抓取完整、清洗干净、编译成可在竞价热路径上 O(1) 查询的索引。这是一道典型的「离线重、在线轻」读写分离题:重活儿全部放在离线,热路径只做纯读——与频次控制工程篇属同一套思路。

① 离线:全网抓取 + 定时刷新。 买方要维护三类数据:

这是一个分布式爬虫 + 存储 + 调度问题:按域名分片抓取、遵守缓存头、错峰刷新(热门域名勤刷、长尾域名稀刷)、处理超时与 404、监控覆盖率。文件并非一成不变——媒体会增删授权、中介会调整 sellers.json,刷新周期直接决定核验的时效与误杀率。sellers.json 还常见大厂文件达数十万条、上百 MB(用 gzip + ETag 增量刷新)、302 跳转至 CDN 等情况。

抓取阶段有两个极易踩中的坑,值得单列:

② 预构索引。 把抓来的原始文件编译成热路径能 O(1) 查的结构,典型三张:

authMap:    (publisher_domain) → set of authorized (asi, sid)   # 来自 ads.txt/app-ads.txt
sellersMap: (asi) → { sid → {domain, seller_type, confidential} } # 来自 sellers.json
bundleMap:  (app.bundle) → developer_domain                      # 来自商店爬取

这三张表就是核验闭环的「弹药库」:authMap 回答「该媒体授权了哪些 (asi,sid)」、sellersMap 回答「某 asi 下某 sid 背后是谁」、bundleMap 将 App 请求翻译为可回查的开发者域名。它们与频次控制的本地缓存一致:离线全量灌入进程内存、定时刷新,在线纯读——下一节的逐跳核验,读的正是这三张表。

四、买方侧工程(在线):热路径逐跳核验

索引备好后,核验就要跑在十万级 QPS 的竞价热路径上。重活儿(全网抓取、解析、建索引)已在上一节离线做完,热路径只需纯读那三张内存索引、亚毫秒完成。

每条 bid request 进来,核验就是对 schain 的一次线性扫描:

extract publisher_domain / bundle + source.schain
for each node in schain:
    seller = sellersMap[node.asi]?.[node.sid]
    if seller == null            → 未知卖方,标记不可信
    if authMap[seller.domain] not contains (node.asi, node.sid)
                                  → 未授权 / 伪造,标记不可信
if !schain.complete or 任一 node 不可信:
    → no-bid / 降权 / 不参与(按买方策略)

几个工程要点:

尽力而为的边界(几个必须承认的现实):

五、透明度之上:供应链优化(SPO)

核验解决「真不真」,schain 还一并解决「值不值」。由于 schain 把真实跳数与每一跳的中介都摊开,买方首次得以回答:同一份库存,究竟是经 3 跳中介买到的,还是经 1 跳 DIRECT 买到的?中间每一跳都在抽成——这正是生态图所讲的 ad tech tax

供应链优化(SPO,Supply Path Optimization) 即买方据此收敛路径:对同一份库存的多条可达路径,优先选择更短、更少抽成、更多 DIRECT、可核验性更高的那条,砍掉冗余的转售跳。这既节省预算(更多预算落到真实库存),也降低风险(路径越短越难藏匿未授权环节)。

因此透明度与 SPO 互为因果:先有可核验的供应链(透明度),才谈得上有依据地优化供应链(SPO)。没有 schain,买方无从得知预算经了几手,优化亦无从着手——这恰好回应了生态图第 7 节那 15% 的 unknown delta:能被测量的浪费,才能被消除

六、常见误区 ↔ 正解

常见误区正解
schain 能替代 ads.txt/sellers.json不能。schain 是运行时路径,那两者是授权声明;核验要三者交叉,缺一不可
complete=1 就一定可信它只是一句可被证伪的承诺;仍要逐 node 回查 sellers.json 和 ads.txt 才算数
schain 里有 node 就代表被授权node 只是「自报经了这一手」;被没被授权,要拿 (asi,sid) 回查 sellers.json + ads.txt 才知道
核验就该「对不上就拒」「查不到」(覆盖/时效/confidential)降权、「明确对不上」才拒——尽力而为,避免误杀真实库存
抓到 HTTP 200 就是有文件很多站点对不存在的路径返回 200 网页 / 软 404;必须校验是纯文本 + 可解析记录(sellers.json 须是 JSON)才算数
schain 只是防欺诈的它同时是 SPO 的数据基础:暴露真实跳数,才能收敛路径、砍 ad tech tax
S2S / Header Bidding 不用管 schain恰恰相反——服务器到服务器的多跳场景更依赖 schain 逐跳生长来还原真实路径(见 Prebid Server)

程序化供应链的核验,静态声明(上篇)回答「谁被授权」,schain(本篇)回答「谁实际参与了」。买方以运行时的每一跳比对事先公布的授权表,域名伪造与库存洗钱便同时失去藏身之处——伪造者要让整条链自洽,须同时骗过媒体的 ads.txt、中介的 sellers.json 与请求的 schain。理解了对账键 (asi, sid) 如何在这三处咬合,也就理解了整个 AdTech 透明度运动的缩影——将每一个「信任来的」环节,逐一换成「可核验的」环节


延伸阅读

一手资料与规范:


views
Share this post on:

Previous Post
RTA 精讲:竞价前实时 API 过滤——第一方数据不出域地影响出价
Next Post
供应链透明度(上):ads.txt / app-ads.txt / sellers.json 把卖方授权变成可交叉核验的声明