Mike Hearn has been lobbying for an "error" message in the Bitcoin p2p protocol for years (at least since the "ban peers if they send us garbage" denial-of-service mitigation code was pull-requested). This came up again with my proposed "smartfee" changes, which would drop low-priority or low-fee transactions.
In short, giving peers feedback about why their blocks or transactions are dropped or why they are being banned should help interoperability between different implementations, and will give SPV (simplified payment verification) clients feedback when their transactions are rejected due to insufficient priority or fees.
I've gone with "reject" as the message, because "error" implies that the peer did something wrong-- and doing something wrong is only one of several possible reasons a connection, transaction, or block may be rejected.
##Specification
One new message type "reject" is introduced. It is sent directly to a peer in response to a "version", "tx" or "block" message.
For example, the message flow between two peers for a relayed transaction that is rejected for some reason would be:
--> "inv"
<-- "getdata"
--> "tx"
<-- "reject"
Every "reject" message begins with the following fields. Some messages append extra, message-specific data.
Field Size | Description | Data type | Comments |
? | response-to-msg | var_str | Message that triggered the reject |
1 | reject-code | uint8_t | |
? | reason | var_str | Human-readable message for debugging |
The human-readable string is intended only for debugging purposes; in particular, different implementations may use different strings. The string should not be shown to users or used for anthing besides diagnosing interoperability problems.
The following reject code categories are used; in the descriptions below, "server" is the peer generating the reject message, "client" is the peer that will receive the message.
Range | Category |
0x01-0x0f | Protocol syntax errors |
0x10-0x1f | Protocol semantic errors |
0x40-0x4f | Server policy rule |
Code | Description |
0x01 | Message could not be decoded |
Codes generated during the intial connection process in response to a "version" message:
Code | Description |
0x11 | Client is an obsolete, unsupported version |
0x12 | Duplicate version message received |
Transaction rejection messages extend the basic message with the transaction id:
Field Size | Description | Data type | Comments |
32 | txid | char[32] | transaction that is rejected |
The following codes are used:
Code | description |
0x10 | Transaction is invalid for some reason (invalid signature, output value greater than input, etc.) |
0x40 | Not mined/relayed because it is "non-standard" (type or version unknown by the server) |
0x41 | One or more output amounts are below the 'dust' threshold |
0x42 | Transaction does not have enough fee/priority to be relayed or mined |
Block rejection messages extend the basic message with the block header hash:
Field Size | Description | Data type | Comments |
32 | hash | char[32] | block (hash of block header) that is rejected |
Rejection codes:
code | description |
0x10 | Block is invalid for some reason (invalid proof-of-work, invalid signature, etc) |
0x11 | Block's version is no longer supported |
0x43 | Inconsistent with a compiled-in checkpoint |
Note: blocks that are not part of the server's idea of the current best chain, but are otherwise valid, should not trigger "reject" messages.
Sending "reject" messages to old nodes does no harm-- unknown commands are ignored for extensibility (in the reference implementation, at least-- other implementations should do the same). So there is no need to bump the protocol version.
Implementors should consider what happens if an attacker either sends them "reject" messages for valid transactions/blocks or sends them random "reject" messages, and should beware of possible denial-of-service attacks (e.g. blindly notifying the user of every "reject" message received would definitely be the wrong thing to do, and even blindly writing every "reject" message to a debug.log could open up a fill-up-disk DoS attack).
Block rejection strings are a bad idea: whether or not a block is valid isn't a matter of opinion, it's defined by the protocol specification and current state of the blockchain. If a peer isn't willing to accept a block, that's because either you or they are forked; put another way, a block being rejected is an error by one or the other party except in the case of forks. (note how almost all of the rejection messages are things you should have been able to validate yourself) Giving our peers reasons gives opportunities to take action in response, which creates a whole new kind of consensus through rejection messages passing around information that we certainly don't want to make possible.
Having said that, responding to a block that passes the context independent AcceptBlock() tests, but is rejected because it wouldn't be the best blockchain tip, with what we do consider to be that tip is probably reasonable and would let that peer more quickly find out they are out of date.
ACK other reject strings.