Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
203 changes: 203 additions & 0 deletions .github/workflows/rhc-p2-batch8-clean.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
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'
RHC_RPC: 'https://rpc.mainnet.chain.robinhood.com'
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: 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: 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<attempts:
time.sleep(min(15,2**attempt)); continue
return last
except Exception as exc:
last={'http_status':0,'error':repr(exc)}
if attempt+1<attempts:
time.sleep(min(15,2**attempt)); continue
return last
return last or {'http_status':0,'error':'unknown'}

def padded(a): return '0x'+'0'*24+a[2:]
def nft_only(rows):
return [r for r in rows if isinstance(r,dict) and isinstance(r.get('topics'),list) and len(r['topics'])>=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'
import csv, json
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'])
got={x['wallet'] for x in direct['wallets']}
failures=[]
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'],
'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')
print(json.dumps(report))
if failures:
raise SystemExit(1)
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
Loading