bootstrap_cross_host_controls.py (4066B)
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,'compiler bundle 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 status=json.loads((copy/'status.json').read_text()) 48 digest=status['artifacts']['build-stable.arboricx']['tree'] 49 obj=copy/'cas/objects'/digest[:3]/digest 50 original_object=obj.read_bytes(); obj.write_bytes(b'corrupt') 51 reject('corrupt-object',None) 52 obj.write_bytes(original_object) 53 package=copy/'build-stable.arboricx'; original_package=package.read_bytes() 54 package.write_bytes(original_package[:-1]+bytes([original_package[-1]^1])) 55 status['artifacts']['build-stable.arboricx']['sha256']=sha(package) 56 (copy/'status.json').write_text(json.dumps(status)) 57 reject('different-authenticated-package','cross-host packaged artifact mismatch') 58 package.write_bytes(original_package) 59 shutil.copyfile(original/'status.json',copy/'status.json') 60 extra=indexed_bytes(encode(987654321)) 61 digest=hashlib.sha256(b'arboricx.indexed-tree.v1\0'+extra).hexdigest() 62 obj=copy/'cas/objects'/digest[:3]/digest 63 assert not obj.exists(); obj.parent.mkdir(exist_ok=True); obj.write_bytes(extra) 64 reject('additional-valid-object','complete canonical object/receipt inventory differs') 65 obj.unlink() 66 # The restored copy must pass, so failure is not caused merely by relocation. 67 verify_cross(first,second,control,copy,runtime,library,source) 68 report.update(complete=True,restored_copy_passes=True) 69 print('PASS: '+str(len(report['rejected']))+' cross-host rejection controls; restored copy accepted') 70 finally: (a.directory/'report.json').write_text(json.dumps(report,indent=2)+'\n') 71 72 73 if __name__=='__main__': main()