mirror of
https://github.com/dashpay/dash.git
synced 2024-12-26 04:22:55 +01:00
96426b5057
8b8d8eeae9 Remove travis_wait from lint script (Graham Krizek) Pull request description: Using the `travis_wait` command in conjunction with `set -o errexit` causes problems. The `travis_wait` command will correctly log the command's output if successful, but if the command fails the process exits before the `travis_wait` command can dump the logs. This will hide important debugging information like error messages and stack traces. We ran into this in #15196 and it was very hard to debug because output was being suppressed. `travis_wait` was being used because the `contrib/verify-commits/verify-commits.py` script can sometimes run for a long time without producing any output. If a script runs for 10 minutes without logging anything, the CI run times out. The `travis_wait` command will extend this timeout by logging a message for you, while sending stderr and stdout to a file. This PR removes the `travis_wait` command from our CI system and adds additional logging to the `verify-commits.py` script so it doesn't make Travis timeout. ACKs for commit 8b8d8e: MarcoFalke: utACK 8b8d8eeae9e8feff6d78420ee172c820ccef9db1 Tree-SHA512: 175a8dd3f4d4e03ab272ddba94fa8bb06875c9027c3f3f81577feda4bc8918b5f0e003a19027f04f8cf2d0b56c68633716a6ab23f95b910121a8d1132428767d
166 lines
8.0 KiB
Python
Executable File
166 lines
8.0 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Copyright (c) 2018 The Bitcoin Core developers
|
|
# Distributed under the MIT software license, see the accompanying
|
|
# file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
"""Verify commits against a trusted keys list."""
|
|
import argparse
|
|
import hashlib
|
|
import logging
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
GIT = os.getenv('GIT', 'git')
|
|
|
|
def tree_sha512sum(commit='HEAD'):
|
|
"""Calculate the Tree-sha512 for the commit.
|
|
|
|
This is copied from github-merge.py."""
|
|
|
|
# request metadata for entire tree, recursively
|
|
files = []
|
|
blob_by_name = {}
|
|
for line in subprocess.check_output([GIT, 'ls-tree', '--full-tree', '-r', commit]).splitlines():
|
|
name_sep = line.index(b'\t')
|
|
metadata = line[:name_sep].split() # perms, 'blob', blobid
|
|
assert metadata[1] == b'blob'
|
|
name = line[name_sep + 1:]
|
|
files.append(name)
|
|
blob_by_name[name] = metadata[2]
|
|
|
|
files.sort()
|
|
# open connection to git-cat-file in batch mode to request data for all blobs
|
|
# this is much faster than launching it per file
|
|
p = subprocess.Popen([GIT, 'cat-file', '--batch'], stdout=subprocess.PIPE, stdin=subprocess.PIPE)
|
|
overall = hashlib.sha512()
|
|
for f in files:
|
|
blob = blob_by_name[f]
|
|
# request blob
|
|
p.stdin.write(blob + b'\n')
|
|
p.stdin.flush()
|
|
# read header: blob, "blob", size
|
|
reply = p.stdout.readline().split()
|
|
assert reply[0] == blob and reply[1] == b'blob'
|
|
size = int(reply[2])
|
|
# hash the blob data
|
|
intern = hashlib.sha512()
|
|
ptr = 0
|
|
while ptr < size:
|
|
bs = min(65536, size - ptr)
|
|
piece = p.stdout.read(bs)
|
|
if len(piece) == bs:
|
|
intern.update(piece)
|
|
else:
|
|
raise IOError('Premature EOF reading git cat-file output')
|
|
ptr += bs
|
|
dig = intern.hexdigest()
|
|
assert p.stdout.read(1) == b'\n' # ignore LF that follows blob data
|
|
# update overall hash with file hash
|
|
overall.update(dig.encode("utf-8"))
|
|
overall.update(" ".encode("utf-8"))
|
|
overall.update(f)
|
|
overall.update("\n".encode("utf-8"))
|
|
p.stdin.close()
|
|
if p.wait():
|
|
raise IOError('Non-zero return value executing git cat-file')
|
|
return overall.hexdigest()
|
|
|
|
def main():
|
|
|
|
# Enable debug logging if running in CI
|
|
if 'CI' in os.environ and os.environ['CI'].lower() == "true":
|
|
logging.getLogger().setLevel(logging.DEBUG)
|
|
|
|
# Parse arguments
|
|
parser = argparse.ArgumentParser(usage='%(prog)s [options] [commit id]')
|
|
parser.add_argument('--disable-tree-check', action='store_false', dest='verify_tree', help='disable SHA-512 tree check')
|
|
parser.add_argument('--clean-merge', type=float, dest='clean_merge', default=float('inf'), help='Only check clean merge after <NUMBER> days ago (default: %(default)s)', metavar='NUMBER')
|
|
parser.add_argument('commit', nargs='?', default='HEAD', help='Check clean merge up to commit <commit>')
|
|
args = parser.parse_args()
|
|
|
|
# get directory of this program and read data files
|
|
dirname = os.path.dirname(os.path.abspath(__file__))
|
|
print("Using verify-commits data from " + dirname)
|
|
verified_root = open(dirname + "/trusted-git-root", "r", encoding="utf8").read().splitlines()[0]
|
|
verified_sha512_root = open(dirname + "/trusted-sha512-root-commit", "r", encoding="utf8").read().splitlines()[0]
|
|
revsig_allowed = open(dirname + "/allow-revsig-commits", "r", encoding="utf-8").read().splitlines()
|
|
unclean_merge_allowed = open(dirname + "/allow-unclean-merge-commits", "r", encoding="utf-8").read().splitlines()
|
|
incorrect_sha512_allowed = open(dirname + "/allow-incorrect-sha512-commits", "r", encoding="utf-8").read().splitlines()
|
|
|
|
# Set commit and branch and set variables
|
|
current_commit = args.commit
|
|
if ' ' in current_commit:
|
|
print("Commit must not contain spaces", file=sys.stderr)
|
|
sys.exit(1)
|
|
verify_tree = args.verify_tree
|
|
no_sha1 = True
|
|
prev_commit = ""
|
|
initial_commit = current_commit
|
|
branch = subprocess.check_output([GIT, 'show', '-s', '--format=%H', initial_commit]).decode('utf8').splitlines()[0]
|
|
|
|
# Iterate through commits
|
|
while True:
|
|
|
|
# Log a message to prevent Travis from timing out
|
|
logging.debug("verify-commits: [in-progress] processing commit {}".format(current_commit[:8]))
|
|
|
|
if current_commit == verified_root:
|
|
print('There is a valid path from "{}" to {} where all commits are signed!'.format(initial_commit, verified_root))
|
|
sys.exit(0)
|
|
if current_commit == verified_sha512_root:
|
|
if verify_tree:
|
|
print("All Tree-SHA512s matched up to {}".format(verified_sha512_root), file=sys.stderr)
|
|
verify_tree = False
|
|
no_sha1 = False
|
|
|
|
os.environ['BITCOIN_VERIFY_COMMITS_ALLOW_SHA1'] = "0" if no_sha1 else "1"
|
|
os.environ['BITCOIN_VERIFY_COMMITS_ALLOW_REVSIG'] = "1" if current_commit in revsig_allowed else "0"
|
|
|
|
# Check that the commit (and parents) was signed with a trusted key
|
|
if subprocess.call([GIT, '-c', 'gpg.program={}/gpg.sh'.format(dirname), 'verify-commit', current_commit], stdout=subprocess.DEVNULL):
|
|
if prev_commit != "":
|
|
print("No parent of {} was signed with a trusted key!".format(prev_commit), file=sys.stderr)
|
|
print("Parents are:", file=sys.stderr)
|
|
parents = subprocess.check_output([GIT, 'show', '-s', '--format=format:%P', prev_commit]).decode('utf8').splitlines()[0].split(' ')
|
|
for parent in parents:
|
|
subprocess.call([GIT, 'show', '-s', parent], stdout=sys.stderr)
|
|
else:
|
|
print("{} was not signed with a trusted key!".format(current_commit), file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
# Check the Tree-SHA512
|
|
if (verify_tree or prev_commit == "") and current_commit not in incorrect_sha512_allowed:
|
|
tree_hash = tree_sha512sum(current_commit)
|
|
if ("Tree-SHA512: {}".format(tree_hash)) not in subprocess.check_output([GIT, 'show', '-s', '--format=format:%B', current_commit]).decode('utf8').splitlines():
|
|
print("Tree-SHA512 did not match for commit " + current_commit, file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
# Merge commits should only have two parents
|
|
parents = subprocess.check_output([GIT, 'show', '-s', '--format=format:%P', current_commit]).decode('utf8').splitlines()[0].split(' ')
|
|
if len(parents) > 2:
|
|
print("Commit {} is an octopus merge".format(current_commit), file=sys.stderr)
|
|
sys.exit(1)
|
|
|
|
# Check that the merge commit is clean
|
|
commit_time = int(subprocess.check_output([GIT, 'show', '-s', '--format=format:%ct', current_commit]).decode('utf8').splitlines()[0])
|
|
check_merge = commit_time > time.time() - args.clean_merge * 24 * 60 * 60 # Only check commits in clean_merge days
|
|
allow_unclean = current_commit in unclean_merge_allowed
|
|
if len(parents) == 2 and check_merge and not allow_unclean:
|
|
current_tree = subprocess.check_output([GIT, 'show', '--format=%T', current_commit]).decode('utf8').splitlines()[0]
|
|
subprocess.call([GIT, 'checkout', '--force', '--quiet', parents[0]])
|
|
subprocess.call([GIT, 'merge', '--no-ff', '--quiet', '--no-gpg-sign', parents[1]], stdout=subprocess.DEVNULL)
|
|
recreated_tree = subprocess.check_output([GIT, 'show', '--format=format:%T', 'HEAD']).decode('utf8').splitlines()[0]
|
|
if current_tree != recreated_tree:
|
|
print("Merge commit {} is not clean".format(current_commit), file=sys.stderr)
|
|
subprocess.call([GIT, 'diff', current_commit])
|
|
subprocess.call([GIT, 'checkout', '--force', '--quiet', branch])
|
|
sys.exit(1)
|
|
subprocess.call([GIT, 'checkout', '--force', '--quiet', branch])
|
|
|
|
prev_commit = current_commit
|
|
current_commit = parents[0]
|
|
|
|
if __name__ == '__main__':
|
|
main()
|