bootstrap_cross_host_controls.py (4480B)
1 #!/usr/bin/env python3 2 """Check verifier rejection of corrupted copies of a completed experiment. 3 Restore the copies and verify acceptance again. 4 """ 5 import argparse 6 import hashlib 7 import json 8 from pathlib import Path 9 import shutil 10 import subprocess 11 from bootstrap_acceptance import sha 12 from bootstrap_cross_host import verify_cross 13 from native_indexed_store import indexed_bytes 14 from base_workloads import encode 15 16 17 def main(): 18 p=argparse.ArgumentParser(description=__doc__) 19 p.add_argument('experiment',type=Path) 20 p.add_argument('--directory',type=Path,required=True) 21 a=p.parse_args(); a.directory.mkdir(parents=True,exist_ok=False) 22 e=a.experiment.resolve(); definition=json.loads((e/'experiment.json').read_text()) 23 runtime=Path(definition['runtime']['path']); library=Path(definition['zig_library']['path']) 24 first,second,control,original=(e/name/'generation' for name in ('H1','H2','H3','Z3')) 25 source=e/'source'; copy=a.directory/'zig-copy' 26 shutil.copytree(original,copy) 27 report=dict(complete=False,rejected=[],acceptance_sha256=sha(e/'accepted-cross-host.json')) 28 def reject(label,expected,zig=copy): 29 try: verify_cross(first,second,control,zig,runtime,library,source) 30 except (ValueError,AssertionError,subprocess.CalledProcessError) as error: 31 if expected and expected not in str(error): raise 32 report['rejected'].append(label) 33 (a.directory/(label+'.log')).write_text(str(error)+'\n'+str(getattr(error,'output',''))) 34 else: raise AssertionError('invalid experiment accepted: '+label) 35 try: 36 reject('same-directory','four distinct',zig=control) 37 for key,value,message in [('execution_host','haskell','execution host mismatch'), 38 ('runtime_sha256','0'*64,'runtime identity mismatch'), 39 ('fresh_store',False,'fresh-store execution evidence'), 40 ('compiler_bundle_sha256','0'*64,'execution request identity mismatch')]: 41 saved={name:(copy/name).read_bytes() for name in ('inputs.json','status.json')} 42 for name,payload in saved.items(): 43 record=json.loads(payload); record[key]=value 44 (copy/name).write_text(json.dumps(record)) 45 reject(key,message) 46 for name,payload in saved.items(): (copy/name).write_bytes(payload) 47 request_path=copy/'execution-request.json' 48 request_bytes=request_path.read_bytes() 49 request=json.loads(request_bytes) 50 request['execution_io_version']=1 if request.get('execution_io_version',1)==2 else 2 51 request_path.write_text(json.dumps(request)) 52 reject('request-io-version','execution request IO version mismatch') 53 request_path.write_bytes(request_bytes) 54 status=json.loads((copy/'status.json').read_text()) 55 digest=status['artifacts']['build-stable.arboricx']['tree'] 56 obj=copy/'cas/objects'/digest[:3]/digest 57 original_object=obj.read_bytes(); obj.write_bytes(b'corrupt') 58 reject('corrupt-object',None) 59 obj.write_bytes(original_object) 60 package=copy/'build-stable.arboricx'; original_package=package.read_bytes() 61 package.write_bytes(original_package[:-1]+bytes([original_package[-1]^1])) 62 status['artifacts']['build-stable.arboricx']['sha256']=sha(package) 63 (copy/'status.json').write_text(json.dumps(status)) 64 reject('different-authenticated-package','cross-host packaged artifact mismatch') 65 package.write_bytes(original_package) 66 shutil.copyfile(original/'status.json',copy/'status.json') 67 extra=indexed_bytes(encode(987654321)) 68 digest=hashlib.sha256(b'arboricx.indexed-tree.v1\0'+extra).hexdigest() 69 obj=copy/'cas/objects'/digest[:3]/digest 70 assert not obj.exists(); obj.parent.mkdir(exist_ok=True); obj.write_bytes(extra) 71 reject('additional-valid-object','complete canonical object/receipt inventory differs') 72 obj.unlink() 73 # The restored copy must pass, so failure is not caused merely by relocation. 74 verify_cross(first,second,control,copy,runtime,library,source) 75 report.update(complete=True,restored_copy_passes=True) 76 print('PASS: '+str(len(report['rejected']))+' cross-host rejection controls; restored copy accepted') 77 finally: (a.directory/'report.json').write_text(json.dumps(report,indent=2)+'\n') 78 79 80 if __name__=='__main__': main()