diff --git a/core/src/main/java/com/google/bitcoin/core/BitcoinSerializer.java b/core/src/main/java/com/google/bitcoin/core/BitcoinSerializer.java index 0d85e049..f3575fb1 100644 --- a/core/src/main/java/com/google/bitcoin/core/BitcoinSerializer.java +++ b/core/src/main/java/com/google/bitcoin/core/BitcoinSerializer.java @@ -70,6 +70,7 @@ public class BitcoinSerializer { names.put(FilteredBlock.class, "merkleblock"); names.put(NotFoundMessage.class, "notfound"); names.put(MemoryPoolMessage.class, "mempool"); + names.put(RejectMessage.class, "reject"); } /** @@ -231,6 +232,8 @@ public class BitcoinSerializer { return new NotFoundMessage(params, payloadBytes); } else if (command.equals("mempool")) { return new MemoryPoolMessage(); + } else if (command.equals("reject")) { + return new RejectMessage(params, payloadBytes); } else { log.warn("No support for deserializing message with name {}", command); return new UnknownMessage(params, command, payloadBytes); diff --git a/core/src/main/java/com/google/bitcoin/core/RejectMessage.java b/core/src/main/java/com/google/bitcoin/core/RejectMessage.java new file mode 100644 index 00000000..d6c486b6 --- /dev/null +++ b/core/src/main/java/com/google/bitcoin/core/RejectMessage.java @@ -0,0 +1,152 @@ +/** + * Copyright 2013 Matt Corallo + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.bitcoin.core; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * A message sent by nodes when a message we sent was rejected (ie a transaction had too little fee/was invalid/etc) + */ +public class RejectMessage extends Message { + private static final long serialVersionUID = -5246995579800334336L; + + private String message, reason; + public static enum RejectCode { + /** The message was not able to be parsed */ + MALFORMED((byte) 0x01), + /** The message described an invalid object */ + INVALID((byte) 0x10), + /** The message was obsolete or described an object which is obsolete (eg unsupported, old version, v1 block) */ + OBSOLETE((byte) 0x11), + /** + * The message was relayed multiple times or described an object which is in conflict with another. + * This message can describe errors in protocol implementation or the presence of an attempt to DOUBLE SPEND. + */ + DUPLICATE((byte) 0x12), + /** + * The message described an object was not standard and was thus not accepted. + * The reference client has a concept of standard transaction forms, which describe scripts and encodings which + * it is willing to relay further. Other transactions are neither relayed nor mined, though they are considered + * valid if they appear in a block. + */ + NONSTANDARD((byte) 0x40), + /** + * This refers to a specific form of NONSTANDARD transactions, which have an output smaller than some constant + * defining them as dust (this is no longer used). + */ + DUST((byte) 0x41), + /** The messages described an object which did not have sufficient fee to be relayed further. */ + INSUFFICIENTFEE((byte) 0x42), + /** The message described a block which was invalid according to hard-coded checkpoint blocks. */ + CHECKPOINT((byte) 0x43), + OTHER((byte) 0xff); + + byte code; + RejectCode(byte code) { this.code = code; } + static RejectCode fromCode(byte code) { + for (RejectCode rejectCode : RejectCode.values()) + if (rejectCode.code == code) + return rejectCode; + return OTHER; + } + } + private RejectCode code; + private Sha256Hash messageHash; + + public RejectMessage(NetworkParameters params, byte[] bytes) throws ProtocolException { + super(params, bytes, 0); + } + + public RejectMessage(NetworkParameters params, byte[] msg, boolean parseLazy, boolean parseRetain, int length) + throws ProtocolException { + super(params, msg, 0, parseLazy, parseRetain, length); + } + + @Override + protected void parseLite() throws ProtocolException { + message = readStr(); + code = RejectCode.fromCode(readBytes(1)[0]); + reason = readStr(); + if (message.equals("block") || message.equals("tx")) + messageHash = readHash(); + length = cursor - offset; + } + + @Override + public void parse() throws ProtocolException { + if (length == UNKNOWN_LENGTH) + parseLite(); + } + + @Override + public void bitcoinSerializeToStream(OutputStream stream) throws IOException { + byte[] messageBytes = message.getBytes("UTF-8"); + stream.write(new VarInt(messageBytes.length).encode()); + stream.write(messageBytes); + stream.write(code.code); + byte[] reasonBytes = reason.getBytes("UTF-8"); + stream.write(new VarInt(reasonBytes.length).encode()); + stream.write(reasonBytes); + if (message.equals("block") || message.equals("tx")) + stream.write(messageHash.getBytes()); + } + + /** + * Provides the type of message which was rejected by the peer. + * Note that this is ENTIRELY UNTRUSTED and should be sanity-checked before it is printed or processed. + */ + public String getRejectedMessage() { + ensureParsed(); + return message; + } + + /** + * Provides the hash of the rejected object (if getRejectedMessage() is either "tx" or "block"), otherwise null. + */ + public Sha256Hash getRejectedObjectHash() { + ensureParsed(); + return messageHash; + } + + /** + * The reason code given for why the peer rejected the message. + */ + public RejectCode getReasonCode() { + return code; + } + + /** + * The reason message given for rejection. + * Note that this is ENTIRELY UNTRUSTED and should be sanity-checked before it is printed or processed. + */ + public String getReasonString() { + return reason; + } + + @Override + public boolean equals(Object o) { + return o instanceof RejectMessage && + ((RejectMessage) o).message.equals(message) && + ((RejectMessage) o).code.equals(code) && + ((RejectMessage) o).reason.equals(reason) && + ((RejectMessage) o).messageHash.equals(messageHash); + } +}