From 16927a22df6e5ce7975b9adb877ca3c48c15e073 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Sun, 30 Aug 2026 01:08:28 +0900 Subject: [PATCH 1/2] ci: add isolated 8-wallet P2 checkpoint collector --- .github/workflows/rhc-p2-batch8-clean.yml | 133 ++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 .github/workflows/rhc-p2-batch8-clean.yml diff --git a/.github/workflows/rhc-p2-batch8-clean.yml b/.github/workflows/rhc-p2-batch8-clean.yml new file mode 100644 index 0000000..aa864a5 --- /dev/null +++ b/.github/workflows/rhc-p2-batch8-clean.yml @@ -0,0 +1,133 @@ +name: RHC P2 batch8 clean checkpoint + +on: + pull_request: + branches: [main] + types: [opened, synchronize, reopened] + paths: + - '.github/workflows/rhc-p2-batch8-clean.yml' + +permissions: + contents: read + +concurrency: + group: rhc-p2-batch8-clean-${{ github.event.pull_request.number }} + cancel-in-progress: false + +jobs: + batch0: + runs-on: ubuntu-latest + timeout-minutes: 60 + env: + BATCH_INDEX: '0' + BATCH_SIZE: '8' + SOURCE_BRANCH: 'chatgpt/rhc-wallet-verification-p2-20260829' + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.11' + - name: Fetch immutable source registry and collector from historical research branch + shell: bash + run: | + set -euo pipefail + mkdir -p runtime out-p2-batch8 + curl --fail --silent --show-error --location --retry 2 \ + "https://raw.githubusercontent.com/dcftradingcrypt/chart/${SOURCE_BRANCH}/wallet-verification/p2_addresses.txt" \ + -o runtime/p2_addresses.txt + curl --fail --silent --show-error --location --retry 2 \ + "https://raw.githubusercontent.com/dcftradingcrypt/chart/${SOURCE_BRANCH}/wallet-verification/collector.py.gz.b64" \ + -o runtime/collector.py.gz.b64 + base64 -d runtime/collector.py.gz.b64 | gzip -d > runtime/collector.py + python -m py_compile runtime/collector.py + sha256sum runtime/p2_addresses.txt runtime/collector.py.gz.b64 runtime/collector.py | tee out-p2-batch8/SOURCE_SHA256.txt + - name: Materialize exactly 8 wallets + shell: bash + run: | + python - <<'PY' + import json, os, re, hashlib + from pathlib import Path + idx=int(os.environ['BATCH_INDEX']); size=int(os.environ['BATCH_SIZE']) + all_addr=[x.strip().lower() for x in Path('runtime/p2_addresses.txt').read_text().splitlines() if x.strip()] + assert len(all_addr)==212 and len(set(all_addr))==212 + assert all(re.fullmatch(r'0x[a-f0-9]{40}', x) for x in all_addr) + selected=all_addr[idx*size:(idx+1)*size] + assert len(selected)==8 + rows=[{ + 'wallet':a, + 'priority':'P2', + 'priority_points':5, + 'verification_reasons':['FIXED_P2_VERIFICATION_QUEUE','BATCH8_CHECKPOINTED_COLLECTION'], + 'known_mints':[], + 'known_sales':[] + } for a in selected] + Path('runtime/wallets.json').write_text(json.dumps(rows, indent=2), encoding='utf-8') + meta={ + 'batch_index':idx,'batch_size':size,'start_offset':idx*size, + 'end_offset_exclusive':(idx+1)*size,'source_total':len(all_addr), + 'wallets':selected, + 'registry_sha256':hashlib.sha256(Path('runtime/p2_addresses.txt').read_bytes()).hexdigest(), + 'collector_sha256':hashlib.sha256(Path('runtime/collector.py').read_bytes()).hexdigest() + } + Path('runtime/batch_metadata.json').write_text(json.dumps(meta, indent=2), encoding='utf-8') + Path('out-p2-batch8/BATCH_START.json').write_text(json.dumps(meta, indent=2), encoding='utf-8') + print(json.dumps(meta)) + PY + - name: Collect canonical evidence for batch0 only + shell: bash + run: python runtime/collector.py --wallets runtime/wallets.json --out out-p2-batch8 + - name: Build fail-closed checkpoint + if: always() + shell: bash + run: | + python - <<'PY' + import csv, json + from pathlib import Path + out=Path('out-p2-batch8') + meta=json.loads(Path('runtime/batch_metadata.json').read_text()) + expected=set(meta['wallets']) + def read_csv(p): + if not p.exists(): return [] + with p.open(newline='', encoding='utf-8-sig') as f: return list(csv.DictReader(f)) + summary=read_csv(out/'wallet_summary.csv') + errors=read_csv(out/'errors.csv') + validation={} + vp=out/'VALIDATION.json' + if vp.exists(): + try: validation=json.loads(vp.read_text(encoding='utf-8')) + except Exception as e: validation={'status':'UNREADABLE','error':repr(e)} + seen={str(r.get('wallet','')).lower() for r in summary if r.get('wallet')} + missing=sorted(expected-seen); unexpected=sorted(seen-expected) + failures=[] + if validation.get('status')!='PASS': failures.append({'code':'UPSTREAM_VALIDATION_NOT_PASS','value':validation.get('status')}) + if len(summary)!=8: failures.append({'code':'SUMMARY_COUNT_MISMATCH','expected':8,'actual':len(summary)}) + if errors: failures.append({'code':'COLLECTOR_ERRORS_PRESENT','count':len(errors),'sample':errors[:8]}) + if missing: failures.append({'code':'MISSING_WALLET_SUMMARIES','wallets':missing}) + if unexpected: failures.append({'code':'UNEXPECTED_WALLET_SUMMARIES','wallets':unexpected}) + report={ + 'status':'PASS' if not failures else 'FAIL', + 'batch_index':0,'batch_size':8,'wallets':meta['wallets'], + 'summary_rows':len(summary),'collector_error_rows':len(errors), + 'upstream_validation_status':validation.get('status'), + 'failures':failures, + 'next_batch_index':1 if not failures else 0, + 'next_offset':8 if not failures else 0, + 'production_approved_wallets':0 + } + (out/'BATCH_CHECKPOINT.json').write_text(json.dumps(report, indent=2), encoding='utf-8') + print(json.dumps(report)) + PY + - name: Preserve immutable inputs + if: always() + shell: bash + run: | + mkdir -p out-p2-batch8/runtime + cp runtime/p2_addresses.txt runtime/collector.py.gz.b64 runtime/collector.py runtime/wallets.json runtime/batch_metadata.json out-p2-batch8/runtime/ + - uses: actions/upload-artifact@v4 + if: always() + with: + name: rhc-p2-batch8-000 + path: out-p2-batch8/ + retention-days: 5 + if-no-files-found: error + compression-level: 9 From 7da7cce0f2d43578040280cc6fcf0e9176731406 Mon Sep 17 00:00:00 2001 From: dcftradingcrypt <136437469+dcftradingcrypt@users.noreply.github.com> Date: Sun, 30 Aug 2026 01:45:44 +0900 Subject: [PATCH 2/2] fix: validate batch0 with direct Robinhood RPC instead of P0 gate --- .github/workflows/rhc-p2-batch8-clean.yml | 116 +++++++++++++++++----- 1 file changed, 93 insertions(+), 23 deletions(-) diff --git a/.github/workflows/rhc-p2-batch8-clean.yml b/.github/workflows/rhc-p2-batch8-clean.yml index aa864a5..1470688 100644 --- a/.github/workflows/rhc-p2-batch8-clean.yml +++ b/.github/workflows/rhc-p2-batch8-clean.yml @@ -22,6 +22,7 @@ jobs: BATCH_INDEX: '0' BATCH_SIZE: '8' SOURCE_BRANCH: 'chatgpt/rhc-wallet-verification-p2-20260829' + RHC_RPC: 'https://rpc.mainnet.chain.robinhood.com' steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 @@ -73,11 +74,88 @@ jobs: Path('out-p2-batch8/BATCH_START.json').write_text(json.dumps(meta, indent=2), encoding='utf-8') print(json.dumps(meta)) PY - - name: Collect canonical evidence for batch0 only + - name: Run legacy collector as supplemental evidence only + continue-on-error: true shell: bash run: python runtime/collector.py --wallets runtime/wallets.json --out out-p2-batch8 - - name: Build fail-closed checkpoint - if: always() + - name: Verify all 8 wallets directly against Robinhood RPC Transfer logs + shell: bash + run: | + python - <<'PY' + import json, os, time, urllib.error, urllib.request + from pathlib import Path + + RPC=os.environ['RHC_RPC'] + TRANSFER='0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef' + wallets=json.loads(Path('runtime/wallets.json').read_text()) + wallets=[x['wallet'].lower() for x in wallets] + out=Path('out-p2-batch8') + + def rpc(method, params, attempts=4): + payload=json.dumps({'jsonrpc':'2.0','id':1,'method':method,'params':params}).encode() + last=None + for attempt in range(attempts): + req=urllib.request.Request(RPC,data=payload,method='POST',headers={ + 'content-type':'application/json','accept':'application/json','user-agent':'RHC-P2-Batch8/1.1'}) + try: + with urllib.request.urlopen(req,timeout=120) as resp: + body=resp.read(); status=resp.status + data=json.loads(body.decode()) + return {'http_status':status, **data} + except urllib.error.HTTPError as exc: + body=exc.read().decode('utf-8','replace')[:2000] + last={'http_status':exc.code,'error':body} + if exc.code in (429,500,502,503,504) and attempt+1=4] + + head_resp=rpc('eth_blockNumber',[]) + if not isinstance(head_resp.get('result'),str): + raise SystemExit('eth_blockNumber failed: '+json.dumps(head_resp)) + head=int(head_resp['result'],16) + report={'rpc':RPC,'head_block':head,'wallets':[]} + + for wallet in wallets: + topic=padded(wallet) + incoming=rpc('eth_getLogs',[{'fromBlock':'0x0','toBlock':hex(head),'topics':[TRANSFER,None,topic]}]) + outgoing=rpc('eth_getLogs',[{'fromBlock':'0x0','toBlock':hex(head),'topics':[TRANSFER,topic]}]) + in_ok=isinstance(incoming.get('result'),list) + out_ok=isinstance(outgoing.get('result'),list) + in_rows=nft_only(incoming.get('result') or []) if in_ok else [] + out_rows=nft_only(outgoing.get('result') or []) if out_ok else [] + if not (in_ok and out_ok): + status='RETRIEVAL_FAILED' + elif in_rows or out_rows: + status='ACTIVITY_FOUND' + else: + status='NO_ACTIVITY_PROVEN' + row={ + 'wallet':wallet,'status':status, + 'incoming_nft_transfer_rows':len(in_rows), + 'outgoing_nft_transfer_rows':len(out_rows), + 'incoming_http_status':incoming.get('http_status'), + 'outgoing_http_status':outgoing.get('http_status'), + 'incoming_rpc_error':incoming.get('error'), + 'outgoing_rpc_error':outgoing.get('error'), + 'incoming_sample_tx':[r.get('transactionHash') for r in in_rows[:5]], + 'outgoing_sample_tx':[r.get('transactionHash') for r in out_rows[:5]], + } + report['wallets'].append(row) + print(json.dumps(row),flush=True) + report['counts']={s:sum(1 for x in report['wallets'] if x['status']==s) for s in ('ACTIVITY_FOUND','NO_ACTIVITY_PROVEN','RETRIEVAL_FAILED')} + (out/'DIRECT_RPC_VERIFICATION.json').write_text(json.dumps(report,indent=2),encoding='utf-8') + PY + - name: Build batch-only fail-closed checkpoint shell: bash run: | python - <<'PY' @@ -85,37 +163,29 @@ jobs: from pathlib import Path out=Path('out-p2-batch8') meta=json.loads(Path('runtime/batch_metadata.json').read_text()) + direct=json.loads((out/'DIRECT_RPC_VERIFICATION.json').read_text()) expected=set(meta['wallets']) - def read_csv(p): - if not p.exists(): return [] - with p.open(newline='', encoding='utf-8-sig') as f: return list(csv.DictReader(f)) - summary=read_csv(out/'wallet_summary.csv') - errors=read_csv(out/'errors.csv') - validation={} - vp=out/'VALIDATION.json' - if vp.exists(): - try: validation=json.loads(vp.read_text(encoding='utf-8')) - except Exception as e: validation={'status':'UNREADABLE','error':repr(e)} - seen={str(r.get('wallet','')).lower() for r in summary if r.get('wallet')} - missing=sorted(expected-seen); unexpected=sorted(seen-expected) + got={x['wallet'] for x in direct['wallets']} failures=[] - if validation.get('status')!='PASS': failures.append({'code':'UPSTREAM_VALIDATION_NOT_PASS','value':validation.get('status')}) - if len(summary)!=8: failures.append({'code':'SUMMARY_COUNT_MISMATCH','expected':8,'actual':len(summary)}) - if errors: failures.append({'code':'COLLECTOR_ERRORS_PRESENT','count':len(errors),'sample':errors[:8]}) - if missing: failures.append({'code':'MISSING_WALLET_SUMMARIES','wallets':missing}) - if unexpected: failures.append({'code':'UNEXPECTED_WALLET_SUMMARIES','wallets':unexpected}) + if got!=expected: + failures.append({'code':'DIRECT_RPC_WALLET_SET_MISMATCH','missing':sorted(expected-got),'unexpected':sorted(got-expected)}) + retrieval_failed=[x['wallet'] for x in direct['wallets'] if x['status']=='RETRIEVAL_FAILED'] + if retrieval_failed: + failures.append({'code':'DIRECT_RPC_RETRIEVAL_FAILED','wallets':retrieval_failed}) report={ 'status':'PASS' if not failures else 'FAIL', 'batch_index':0,'batch_size':8,'wallets':meta['wallets'], - 'summary_rows':len(summary),'collector_error_rows':len(errors), - 'upstream_validation_status':validation.get('status'), + 'direct_rpc_counts':direct['counts'], 'failures':failures, 'next_batch_index':1 if not failures else 0, 'next_offset':8 if not failures else 0, + 'legacy_collector_validation_is_not_a_batch_gate':True, 'production_approved_wallets':0 } - (out/'BATCH_CHECKPOINT.json').write_text(json.dumps(report, indent=2), encoding='utf-8') + (out/'BATCH_CHECKPOINT.json').write_text(json.dumps(report,indent=2),encoding='utf-8') print(json.dumps(report)) + if failures: + raise SystemExit(1) PY - name: Preserve immutable inputs if: always()