forked from hyperledger-labs/perun-eth-backend
/
timeout.go
105 lines (93 loc) · 3.36 KB
/
timeout.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
// Copyright 2020 - See NOTICE file for copyright holders.
//
// 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 channel
import (
"context"
"fmt"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/core/types"
"github.com/pkg/errors"
cherrors "github.com/perun-network/perun-eth-backend/channel/errors"
)
// BlockTimeout is a timeout on an Ethereum blockchain. A ChainReader is used to
// wait for the timeout to pass.
//
// This is much better than a channel.TimeTimeout because the local clock might
// not match the blockchain's timestamp at the point in time when the timeout
// has passed locally.
type BlockTimeout struct {
Time uint64
cr ethereum.ChainReader
}
// NewBlockTimeout creates a new BlockTimeout bound to the provided ChainReader
// and ts as the absolute block.timestamp timeout.
func NewBlockTimeout(cr ethereum.ChainReader, ts uint64) *BlockTimeout {
return &BlockTimeout{
Time: ts,
cr: cr,
}
}
// NewBlockTimeoutDuration creates a new BlockTimeout that elapses duration
// blockchain seconds added to the current block timestamp in the future.
func NewBlockTimeoutDuration(ctx context.Context, cr ethereum.ChainReader, duration uint64) (*BlockTimeout, error) {
h, err := cr.HeaderByNumber(ctx, nil)
if err != nil {
err = cherrors.CheckIsChainNotReachableError(err)
return nil, errors.WithMessage(err, "getting latest header")
}
return NewBlockTimeout(cr, h.Time+duration), nil
}
// IsElapsed reads the timestamp from the current blockchain header to check
// whether the timeout has passed yet.
func (t *BlockTimeout) IsElapsed(ctx context.Context) bool {
header, err := t.cr.HeaderByNumber(ctx, nil)
if err != nil {
// If there's an error, just return false here. A later Wait on the
// BlockTimeout will expose the error to the caller.
return false
}
return header.Time >= t.Time
}
// Wait subscribes to new blocks until the timeout is reached.
// It returns the context error if it is canceled before the timeout is reached.
func (t *BlockTimeout) Wait(ctx context.Context) error {
headers := make(chan *types.Header)
sub, err := t.cr.SubscribeNewHead(ctx, headers)
if err != nil {
err = cherrors.CheckIsChainNotReachableError(err)
return errors.WithMessage(err, "subscribing to new heads")
}
defer sub.Unsubscribe()
for {
select {
case header := <-headers:
if header.Time >= t.Time {
return nil
}
case err := <-sub.Err():
if err != nil {
err = cherrors.CheckIsChainNotReachableError(err)
return errors.WithMessage(err, "sub done")
}
// make sure we return a non-nil error if the timeout hasn't passed yet
return errors.New("sub done before timeout")
case <-ctx.Done():
return errors.Wrap(ctx.Err(), "context done")
}
}
}
// String returns a string stating the block timeout as a unix timestamp.
func (t *BlockTimeout) String() string {
return fmt.Sprintf("<Block timeout: %d>", t.Time)
}