def parse(cls, stream): # 버전 : 4바이트 리틀엔디언 version = little_endian_to_int(stream.read(4)) # 이전 블록 해시값 : 32바이트 리틀엔디언 prev_block = stream.read(32)[::-1] # 머클루트 : 32 바이트 리틀엔디언 merkle_root = stream.read(32)[::-1] # 타임스탬프 : 4 바이트 리틀엔디언 timestamp = little_endian_to_int(stream.read(4)) # 비트값 : 4바이트 bits = stream.read(4) # 논스값 : 4바이트 nounce = stream.read(4) # 전체 트랜잭션 갯수 total = little_endian_to_int(stream.read(4)) # 해시값 : 가변 정수 및 32바이트 리틀엔디언 num_hashes = read_varint(stream) hashes = [] for _ in range(num_hashes): hashes.append(stream.read(32)[::-1]) # 플래그 비트 : 가변 정수 및 1바이트 num_flags = read_varint(stream) flag_bits = stream.read(num_flags) return cls(version, prev_block, merkle_root, timestamp, bits, nounce, total, hashes, flag_bits)
def parse(cls, stream, testnet=False): """ 직렬화된 정보를 파싱하는 클래스 메서드 :param stream: 직렬화 정보에 대한 스트림 - 직렬화 정보는 네트워크 통신이나 파일 입출력을 통해 받기 때문에 시간이 걸림 - 받아야 하는 트랜잭션 크기(직렬화 크기)가 커지면 받을 때까지 파싱 불가능 - 직렬화 정보의 스트림을 인수로 받아 파싱하면 효율적 i.e 받은 데이터까지 파싱 진행 가능 :param testnet : 테스트넷 여부. 직렬화 정보에는 없는 정보 :return: 역직렬화된 객체 """ # 버전 : 4바이트 리틀엔디언 version_info = stream.read(4) version = little_endian_to_int(version_info) # 사용할 비트코인 : 이전 트랜잭션 정보 num_inputs = read_varint(stream) inputs = [] for _ in range(num_inputs): inputs.append(TxIn.parse(stream)) # 비트코인을 보내는 종착지 : 트랜잭션 출력 정보 num_outputs = read_varint(stream) outputs = [] for _ in range(num_outputs): outputs.append(TxOut.parse(stream)) # 록타임 : 4바이트의 리틀엔디언 locktime = little_endian_to_int(stream.read(4)) return cls(version, inputs, outputs, locktime, testnet=testnet)
def parse_segwit(cls, stream, testnet=False): """ 세그윗 트랜잭션의 직렬화된 정보를 파싱하는 클래스 메서드 - 세그윗 마커 - 입력마다 있는 증인 필드 :param stream: 직렬화 정보에 대한 스트림 :param testnet: 테스트넷 여부. 직렬화 정보에는 없는 정보 :return: 역직렬화된 객체 """ # 버전 : 4바이트 리틀엔디언 version_info = stream.read(4) version = little_endian_to_int(version_info) # 세그윗 마커 정보 : 2바이트 marker = stream.read(2) if marker != b"\x00\x01": raise RuntimeError("Not a segwit transaction : {}".format(marker)) # 사용할 비트코인 : 이전 트랜잭션 정보 num_inputs = read_varint(stream) inputs = [] for _ in range(num_inputs): inputs.append(TxIn.parse(stream)) # 비트코인을 보내는 종착지 : 트랜잭션 출력 정보 num_outputs = read_varint(stream) outputs = [] for _ in range(num_outputs): outputs.append(TxOut.parse(stream)) # 입력마다 주어지는 증인(witness) 필드 for tx_in in inputs: num_items = read_varint(stream) items = [] for _ in range(num_items): item_len = read_varint(stream) if item_len == 0: items.append(0) else: items.append(stream.read(item_len)) tx_in.witness = items # 록타임 : 4바이트의 리틀엔디언 locktime = little_endian_to_int(stream.read(4)) return cls(version, inputs, outputs, locktime, testnet=testnet, segwit=True)
def parse(cls, stream): # 블록 헤더 수 : 가변 정수 형식 num_headers = read_varint(stream) # 블록 헤더 blocks = [] for _ in range(num_headers): blocks.append(Block.parse(stream)) num_transactions = read_varint(stream) # 가정 : 트랜잭션 수는 항상 0 if num_transactions != 0: raise RuntimeError( "Number of transaction should be 0 but {}".format( num_transactions)) return cls(blocks)
def parse(cls, stream): """ 스크립트 파싱 - 첫 1바이트를 읽어 n이라 한다 - 0x01 ~ 0x4b(1~75) 사이의 값인 경우 : 이어서 n바이트 길이를 읽어 해당 숫자를 한 원소로 간주 - 0x4c 인 경우 : OP_PUSHDATA1 에 해당 => 바로 이후의 1 바이트 값이 그 다음 읽을 원소의 길이 정보를 표현 i.e OP_PUSHDATA1 <1바이트 리틀엔디언으로 표현한 원소 길이> <원소> 형태 - 0x4d 인 경우 : OP_PUSHDATA2 에 해당 => 바로 이후의 2 바이트 값이 그 다음 읽을 원소의 길이 정보를 표현 i.e OP_PUSHDATA1 <2바이트 리틀엔디언으로 표현한 원소 길이> <원소> 형태 - 78 이상인 경우 : 해당 바이트(n)은 오피코드(연산자) :param stream: 직렬화한 스크립트의 스트림 :return: 역질력화된 스크립트 객체 """ # 스크립트는 가변 길이 필드로 시작 length = read_varint(stream) commands = [] count = 0 while count < length: current = stream.read(1) count += 1 current_bytes = current[0] # 다음 current_bytes가 한 데이터 원소 if 1 <= current_bytes <= 75: n = current_bytes commands.append(stream.read(n)) count += n # 다음 한 바이트가 파싱할 데이터 원소의 길이 elif current_bytes == 76: data_length = little_endian_to_int(stream.read(1)) commands.append(stream.read(data_length)) count += (data_length + 1) # 다음 두 바이트가 파싱할 데이터 원소의 길이 elif current_bytes == 77: data_length = little_endian_to_int(stream.read(2)) commands.append(stream.read(data_length)) count += (data_length + 2) # 해당 바이트 자체가 오피코드 else: op_code = current_bytes commands.append(op_code) # 스크립트 파싱이 정확하게 되었는지 확인 if count != length: raise SyntaxError("Parsing srcipt failed") return cls(commands)