src/server/pkg/collection/transaction.go
func (s *stm) fetchTTL(iface STM, key string) (int64, error) {
	// check wset cache
	if wv, ok := s.wset[key]; ok {
		return wv.ttl, nil
	}

	// Read ttl through s.ttlset cache
	if ttl, ok := s.ttlset[key]; ok {
		return ttl, nil
	}

	// Read kv and lease ID, and cache new TTL
	getResp := iface.fetch(key) // call correct implementation of fetch()
	if len(getResp.Kvs) == 0 {
		return 0, ErrNotFound{Key: key}
	}
	leaseID := v3.LeaseID(getResp.Kvs[0].Lease)
	if leaseID == 0 {
		s.ttlset[key] = 0 // 0 is default value, but now 'ok' will be true on check
		return 0, nil
	}
	span, ctx := tracing.AddSpanToAnyExisting(s.ctx, "etcd.TimeToLive")
	defer tracing.FinishAnySpan(span)
	leaseResp, err := s.client.TimeToLive(ctx, leaseID)
	if err != nil {
		panic(stmError{err})
	}
	s.ttlset[key] = leaseResp.TTL
	for _, key := range leaseResp.Keys {
		s.ttlset[string(key)] = leaseResp.TTL
	}
	return leaseResp.TTL, nil
}
func (s *stm) fetchTTL(iface STM, key string) (int64, error) {
	// check wset cache
	if wv, ok := s.wset[key]; ok {
		return wv.ttl, nil
	}

	// Read ttl through s.ttlset cache
	if ttl, ok := s.ttlset[key]; ok {
		return ttl, nil
	}

	// Read kv and lease ID, and cache new TTL
	getResp := iface.fetch(key) // call correct implementation of fetch()
	if len(getResp.Kvs) == 0 {
		return 0, ErrNotFound{Key: key}
	}
	leaseID := v3.LeaseID(getResp.Kvs[0].Lease)
	if leaseID == 0 {
		s.ttlset[key] = 0 // 0 is default value, but now 'ok' will be true on check
		return 0, nil
	}
	span, ctx := tracing.AddSpanToAnyExisting(s.ctx, "etcd.TimeToLive")
	defer tracing.FinishAnySpan(span)
	leaseResp, err := s.client.TimeToLive(ctx, leaseID)
	if err != nil {
		panic(stmError{err})
	}
	s.ttlset[key] = leaseResp.TTL
	for _, key := range leaseResp.Keys {
		s.ttlset[string(key)] = leaseResp.TTL
	}
	return leaseResp.TTL, nil
}
// fetchTTL contains the essential implementation of TTL().
//
// Note that 'iface' should either be the receiver 's' or a containing
// 'stmSerializeable'--the only reason 'iface' is passed as a separate argument
// is because fetchTTL calls iface.fetch(), and the implementation of 'fetch' is
// different for stm and stmSerializeable. Passing the interface ensures the
// correct version of fetch() is called