Given that:

  1. According to the white and yellow paper a block's timestamp must be bigger than the parent's.
  2. According to the white paper a block's timestamp can be within 15 min of the parent blocktime.

Would that imply that if a differential block time of 14 min occurs,

  1. the next winning miner has to either fake a timestamp, and this potentially could lead to the blockchain running faster and faster into the future,
  2. or wait until his Unix time catches up.

Is this true? Is there a reason why empirically it never happened?

up vote 12 down vote accepted

Summary

A block mined by a miner that sets their computer time ahead of the current "real" time will have their winning block rejected by other Ethereum nodes. Other miners on the Ethereum network will continue mining on the latest valid block.


Details

According to the code, from Github - Go Ethereum - consensus/ethash/consensus.go, lines 220-284:

// verifyHeader checks whether a header conforms to the consensus rules of the
// stock Ethereum ethash engine.
// See YP section 4.3.4. "Block Header Validity"
func (ethash *Ethash) verifyHeader(chain consensus.ChainReader, header, parent *types.Header, uncle bool, seal bool) error {
  // Ensure that the header's extra-data section is of a reasonable size
  if uint64(len(header.Extra)) > params.MaximumExtraDataSize {
    return fmt.Errorf("extra-data too long: %d > %d", len(header.Extra), params.MaximumExtraDataSize)
  }
  // Verify the header's timestamp
  if uncle {
    if header.Time.Cmp(math.MaxBig256) > 0 {
      return errLargeBlockTime
    }
  } else {
    if header.Time.Cmp(big.NewInt(time.Now().Unix())) > 0 {
      return consensus.ErrFutureBlock
    }
  }
  if header.Time.Cmp(parent.Time) <= 0 {
    return errZeroBlockTime
  }
  // Verify the block's difficulty based in it's timestamp and parent's difficulty
  expected := CalcDifficulty(chain.Config(), header.Time.Uint64(), parent)
  if expected.Cmp(header.Difficulty) != 0 {
    return fmt.Errorf("invalid difficulty: have %v, want %v", header.Difficulty, expected)
  }
  // Verify that the gas limit is <= 2^63-1
  if header.GasLimit.Cmp(math.MaxBig63) > 0 {
    return fmt.Errorf("invalid gasLimit: have %v, max %v", header.GasLimit, math.MaxBig63)
  }
  // Verify that the gasUsed is <= gasLimit
  if header.GasUsed.Cmp(header.GasLimit) > 0 {
    return fmt.Errorf("invalid gasUsed: have %v, gasLimit %v", header.GasUsed, header.GasLimit)
  }

  // Verify that the gas limit remains within allowed bounds
  diff := new(big.Int).Set(parent.GasLimit)
  diff = diff.Sub(diff, header.GasLimit)
  diff.Abs(diff)

  limit := new(big.Int).Set(parent.GasLimit)
  limit = limit.Div(limit, params.GasLimitBoundDivisor)

  if diff.Cmp(limit) >= 0 || header.GasLimit.Cmp(params.MinGasLimit) < 0 {
    return fmt.Errorf("invalid gas limit: have %v, want %v += %v", header.GasLimit, parent.GasLimit, limit)
  }
  // Verify that the block number is parent's +1
  if diff := new(big.Int).Sub(header.Number, parent.Number); diff.Cmp(big.NewInt(1)) != 0 {
    return consensus.ErrInvalidNumber
  }
  // Verify the engine specific seal securing the block
  if seal {
    if err := ethash.VerifySeal(chain, header); err != nil {
      return err
    }
  }
  // If all checks passed, validate any special fields for hard forks
  if err := misc.VerifyDAOHeaderExtraData(chain.Config(), header); err != nil {
    return err
  }
  if err := misc.VerifyForkHashes(chain.Config(), header, uncle); err != nil {
    return err
  }
  return nil
}

the following time validations are done when a new block is received by an Ethereum node:

if (newblock.header.time > thiscomputer.time) {
    error "block in the future"
}

and

if (newblock.header.time <= newblock.parent.header.time) {
    error "timestamp equals parent's"  
}

The error messages are from Github - Go Ethereum - consensus/errors.go, line 28 and Github - Go Ethereum - consensus/ethash/consensus.go, line 50.



Parity Timestamp Check

From Github - Parity - ethcore/src/verification/verification.rs, lines 197-209:

/// Check header parameters agains parent header.
fn verify_parent(header: &Header, parent: &Header) -> Result<(), Error> {
    if !header.parent_hash.is_zero() && parent.hash() != header.parent_hash {
        return Err(From::from(BlockError::InvalidParentHash(Mismatch { expected: parent.hash(), found: header.parent_hash.clone() })))
    }
    if header.timestamp <= parent.timestamp {
        return Err(From::from(BlockError::InvalidTimestamp(OutOfBounds { max: None, min: Some(parent.timestamp + 1), found: header.timestamp })))
    }
    if header.number != parent.number + 1 {
        return Err(From::from(BlockError::InvalidNumber(Mismatch { expected: parent.number + 1, found: header.number })));
    }
    Ok(())
}

So, Parity check that the block timestamp is > parent block timestamp, but does not check if the block timestamp is in the future.

From Github - Parity - ethcore/src/verification/verification.rs, lines 80-84:

/// Phase 3 verification. Check block information against parent and uncles.
pub fn verify_block_family(header: &Header, bytes: &[u8], engine: &Engine, bc: &BlockProvider) -> Result<(), Error> {
    // TODO: verify timestamp
    let parent = try!(bc.block_header(&header.parent_hash).ok_or_else(|| Error::from(BlockError::UnknownParent(header.parent_hash.clone()))));
    try!(verify_parent(&header, &parent));

There is an outstanding TODO to verify the timestamp.

  • So that means Geth is stricter than the specification. they assume node have properly synced timeservers and do not even think there is latency more than a sec. Interesting. What if Parity sees it different? – Roland Kofler Jun 12 '16 at 8:46
  • 1
    @Roland Kofler, I've added the relevant bit of code for Parity at the bottom of my answer. – The Officious BokkyPooBah Jun 12 '16 at 10:57
  • The actual location in geth code is here: github.com/ethereum/go-ethereum/blob/885c13c/consensus/ethash/… – skozin Oct 13 '17 at 16:13
  • @RolandKofler the time stamp simply has to be less than the current time stamp of the node, and greater than the timestamp of the last block. Therefore latency is accounted for, as latency only goes one way -- blocks arrive slowly, not that blocks arrive in the future ;) – Alex Oct 13 '17 at 17:36

If a miner M sees a block B with a timestamp far in the future, here's what they would probably do: instead of building on B, they would publish their own block C with a more accurate timestamp. By doing this, M is likely to get rewarded since others will likely build upon C instead of B.

As mentioned in the question, to build upon block B, a miner, such as M, would have to use a fake timestamp (since a block's timestamp must be bigger than the parent's). Buy why would M build upon B, when doing so will require other miners to use a fake timestamp to build upon M's block? There is more benefit for M to publish a block C with a more accurate timestamp.

M's strategy is assisted by the fact that by default, all Ethereum nodes coordinate around the correct time so M has an idea of whether to build on B or publish their own C.


To connect with @BokkyPooBah's answer, when a node sees B, the node rejects B with BlockFutureErr so that it doesn't produce an invalid block X (where B.timestamp >= X.timestamp) per protocol. But note that there's nothing in the protocol that says anything about the invalidity of a block from having a timestamp far in the future.

  • Thats an awesome reason why! – Roland Kofler Jun 12 '16 at 8:48

Your Answer

 
discard

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Not the answer you're looking for? Browse other questions tagged or ask your own question.