diff --git a/.gitignore b/.gitignore index 5726d68..d3e9c1c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ pkg/**/gen/*/*.go tools/**/gen/*/*.go !tools/generators/promise/gen/*.go +!tools/generators/promise/gen/async/*.go diff --git a/pkg/chain/ethereum/block_counter.go b/pkg/chain/ethereum/block_counter.go index 03d16db..50be8ae 100644 --- a/pkg/chain/ethereum/block_counter.go +++ b/pkg/chain/ethereum/block_counter.go @@ -201,12 +201,12 @@ func (bc *BlockCounter) subscribeBlocks( } }() - lastBlock, err := chainReader.BlockByNumber(ctx, nil) + lastHeader, err := chainReader.HeaderByNumber(ctx, nil) if err != nil { return err } - bc.subscriptionChannel <- block{lastBlock.Number.String()} + bc.subscriptionChannel <- block{lastHeader.Number.String()} return nil } @@ -215,17 +215,17 @@ func (bc *BlockCounter) subscribeBlocks( func CreateBlockCounter(chainReader ChainReader) (*BlockCounter, error) { ctx := context.Background() - startupBlock, err := chainReader.BlockByNumber(ctx, nil) + startupHeader, err := chainReader.HeaderByNumber(ctx, nil) if err != nil { return nil, fmt.Errorf( - "failed to get initial block from the chain: [%v]", + "failed to get initial block header from the chain: [%v]", err, ) } blockCounter := &BlockCounter{ - latestBlockHeight: startupBlock.Number.Uint64(), + latestBlockHeight: startupHeader.Number.Uint64(), waiters: make(map[uint64][]chan uint64), subscriptionChannel: make(chan block), } diff --git a/pkg/chain/ethereum/chain.go b/pkg/chain/ethereum/chain.go index e373390..4688d6a 100644 --- a/pkg/chain/ethereum/chain.go +++ b/pkg/chain/ethereum/chain.go @@ -39,9 +39,9 @@ type Subscription interface { // ChainReader provides access to the blockchain. type ChainReader interface { - // BlockByNumber gets the block by its number. The block number argument - // can be nil to select the latest block. - BlockByNumber(ctx context.Context, number *big.Int) (*Block, error) + // HeaderByNumber gets the block header by its number. The block header + // number argument can be nil to select the latest block header. + HeaderByNumber(ctx context.Context, number *big.Int) (*Header, error) // SubscribeNewHead subscribes to notifications about changes of the // head block of the canonical chain. diff --git a/pkg/chain/ethereum/ethutil/adapter.go b/pkg/chain/ethereum/ethutil/adapter.go index fcf4aa4..d072766 100644 --- a/pkg/chain/ethereum/ethutil/adapter.go +++ b/pkg/chain/ethereum/ethutil/adapter.go @@ -14,19 +14,17 @@ type ethereumAdapter struct { delegate EthereumClient } -func (ea *ethereumAdapter) BlockByNumber( +func (ea *ethereumAdapter) HeaderByNumber( ctx context.Context, number *big.Int, -) (*chainEthereum.Block, error) { - block, err := ea.delegate.BlockByNumber(ctx, number) +) (*chainEthereum.Header, error) { + header, err := ea.delegate.HeaderByNumber(ctx, number) if err != nil { return nil, err } - return &chainEthereum.Block{ - Header: &chainEthereum.Header{ - Number: block.Number(), - }, + return &chainEthereum.Header{ + Number: header.Number, }, nil } diff --git a/pkg/chain/ethereum/ethutil/adapter_test.go b/pkg/chain/ethereum/ethutil/adapter_test.go index d8a72b9..8678b0f 100644 --- a/pkg/chain/ethereum/ethutil/adapter_test.go +++ b/pkg/chain/ethereum/ethutil/adapter_test.go @@ -15,7 +15,7 @@ import ( chainEthereum "github.com/keep-network/keep-common/pkg/chain/ethereum" ) -func TestEthereumAdapter_BlockByNumber(t *testing.T) { +func TestEthereumAdapter_HeaderByNumber(t *testing.T) { client := &mockAdaptedEthereumClient{ blocks: []*big.Int{ big.NewInt(0), @@ -31,35 +31,35 @@ func TestEthereumAdapter_BlockByNumber(t *testing.T) { adapter := ðereumAdapter{client} - blockOne, err := adapter.BlockByNumber(context.Background(), big.NewInt(1)) + headerOne, err := adapter.HeaderByNumber(context.Background(), big.NewInt(1)) if err != nil { t.Fatal(err) } - lastBlock, err := adapter.BlockByNumber(context.Background(), nil) + lastHeader, err := adapter.HeaderByNumber(context.Background(), nil) if err != nil { t.Fatal(err) } - expectedBlockOneNumber := big.NewInt(1) - if expectedBlockOneNumber.Cmp(blockOne.Number) != 0 { + expectedHeaderOneNumber := big.NewInt(1) + if expectedHeaderOneNumber.Cmp(headerOne.Number) != 0 { t.Errorf( - "unexpected block number\n"+ + "unexpected header number\n"+ "expected: [%v]\n"+ "actual: [%v]", - expectedBlockOneNumber, - blockOne.Number, + expectedHeaderOneNumber, + headerOne.Number, ) } - expectedLastBlockNumber := big.NewInt(2) - if expectedLastBlockNumber.Cmp(lastBlock.Number) != 0 { + expectedLastHeaderNumber := big.NewInt(2) + if expectedLastHeaderNumber.Cmp(lastHeader.Number) != 0 { t.Errorf( - "unexpected last block number\n"+ + "unexpected last header number\n"+ "expected: [%v]\n"+ "actual: [%v]", - expectedLastBlockNumber, - lastBlock.Number, + expectedLastHeaderNumber, + lastHeader.Number, ) } } @@ -166,22 +166,20 @@ type mockAdaptedEthereumClient struct { nonces map[common.Address]uint64 } -func (maec *mockAdaptedEthereumClient) BlockByNumber( +func (maec *mockAdaptedEthereumClient) HeaderByNumber( ctx context.Context, number *big.Int, -) (*types.Block, error) { +) (*types.Header, error) { index := len(maec.blocks) - 1 if number != nil { index = int(number.Int64()) } - return types.NewBlockWithHeader( - &types.Header{ - Number: maec.blocks[index], - BaseFee: maec.blocksBaseFee[index], - }, - ), nil + return &types.Header{ + Number: maec.blocks[index], + BaseFee: maec.blocksBaseFee[index], + }, nil } func (maec *mockAdaptedEthereumClient) SubscribeNewHead( diff --git a/pkg/chain/ethereum/ethutil/mining_waiter.go b/pkg/chain/ethereum/ethutil/mining_waiter.go index 93cf3e0..a38761a 100644 --- a/pkg/chain/ethereum/ethutil/mining_waiter.go +++ b/pkg/chain/ethereum/ethutil/mining_waiter.go @@ -33,9 +33,9 @@ var ( // increase the transaction's chance for being picked up by miners. // // Specific action depends on transaction type: -// - legacy pre EIP-1559 transaction: bumps up the gas price by 20% -// - dynamic fee post EIP-1559 transaction: bumps up the gas tip cap by 20% -// and adjusts the gas fee cap accordingly +// - legacy pre EIP-1559 transaction: bumps up the gas price by 20% +// - dynamic fee post EIP-1559 transaction: bumps up the gas tip cap by 20% +// and adjusts the gas fee cap accordingly type MiningWaiter struct { client EthereumClient checkInterval time.Duration @@ -391,15 +391,15 @@ func (mw *MiningWaiter) forceMiningDynamicFeeTx( } func (mw *MiningWaiter) latestBaseFee() (*big.Int, error) { - latestBlock, err := mw.client.BlockByNumber( + latestHeader, err := mw.client.HeaderByNumber( context.Background(), nil, ) if err != nil { - return nil, fmt.Errorf("could not get the latest block: [%v]", err) + return nil, fmt.Errorf("could not get the latest block header: [%v]", err) } - baseFee := latestBlock.BaseFee() + baseFee := latestHeader.BaseFee if baseFee == nil { return nil, fmt.Errorf("not an EIP-1559 block") } diff --git a/tools/generators/promise/gen/async/big_int_promise.go b/tools/generators/promise/gen/async/big_int_promise.go new file mode 100644 index 0000000..00cad2d --- /dev/null +++ b/tools/generators/promise/gen/async/big_int_promise.go @@ -0,0 +1,152 @@ +// Package async contains promise implementations generated for specific types. +// This is auto generated code. +package async + +import ( + "fmt" + "math/big" + "sync" +) + +// BigIntPromise represents an eventual completion of an ansynchronous +// operation and its resulting value. Promise can be either fulfilled or +// failed and it can happen only one time. All Promise operations are +// thread-safe. To create a promise use: `&BigIntPromise{}` +type BigIntPromise struct { + mutex sync.Mutex + successFn func(*big.Int) + failureFn func(error) + completeFn func(*big.Int, error) + + isComplete bool + value *big.Int + err error +} + +// OnSuccess registers a function to be called when the Promise +// has been fulfilled. In case of a failed Promise, function is not +// called at all. OnSuccess is a non-blocking operation. Only one on success +// function can be registered for a Promise. If the Promise has been already +// fulfilled, the function is called immediatelly. +func (p *BigIntPromise) OnSuccess(onSuccess func(*big.Int)) *BigIntPromise { + p.mutex.Lock() + defer p.mutex.Unlock() + + p.successFn = onSuccess + + if p.isComplete && p.err == nil { + p.callSuccessFn() + } + + return p +} + +// OnFailure registers a function to be called when the Promise +// execution failed. In case of a fulfilled Promise, function is not +// called at all. OnFailure is a non-blocking operation. Only one on failure +// function can be registered for a Promise. If the Promise has already failed, +// the function is called immediatelly. +func (p *BigIntPromise) OnFailure(onFailure func(error)) *BigIntPromise { + p.mutex.Lock() + defer p.mutex.Unlock() + + p.failureFn = onFailure + + if p.isComplete && p.err != nil { + p.callFailureFn() + } + + return p +} + +// OnComplete registers a function to be called when the Promise +// execution completed no matter if it succeded or failed. +// In case of a successful execution, error passed to the callback +// function is nil. In case of a failed execution, there is no +// value evaluated so the value parameter is nil. OnComplete is +// a non-blocking operation. Only one on complete function can be +// registered for a Promise. If the Promise has already completed, +// the function is called immediatelly. +func (p *BigIntPromise) OnComplete(onComplete func(*big.Int, error)) *BigIntPromise { + p.mutex.Lock() + defer p.mutex.Unlock() + + p.completeFn = onComplete + + if p.isComplete { + p.callCompleteFn() + } + + return p +} + +// Fulfill can happen only once for a Promise and it results in calling +// the OnSuccess callback, if registered. If Promise has been already +// completed by either fulfilling or failing, this function reports +// an error. +func (p *BigIntPromise) Fulfill(value *big.Int) error { + p.mutex.Lock() + defer p.mutex.Unlock() + + if p.isComplete { + return fmt.Errorf("promise already completed") + } + + p.isComplete = true + p.value = value + + p.callSuccessFn() + p.callCompleteFn() + + return nil +} + +// Fail can happen only once for a Promise and it results in calling +// the OnFailure callback, if registered. If Promise has been already +// completed by either fulfilling or failing, this function reports +// an error. Also, this function reports an error if `err` parameter +// is `nil`. +func (p *BigIntPromise) Fail(err error) error { + p.mutex.Lock() + defer p.mutex.Unlock() + + if err == nil { + return fmt.Errorf("error cannot be nil") + } + + if p.isComplete { + return fmt.Errorf("promise already completed") + } + + p.isComplete = true + p.err = err + + p.callFailureFn() + p.callCompleteFn() + + return nil +} + +func (p *BigIntPromise) callCompleteFn() { + if p.completeFn != nil { + go func() { + p.completeFn(p.value, p.err) + }() + } +} + +func (p *BigIntPromise) callSuccessFn() { + if p.successFn != nil { + go func() { + p.successFn(p.value) + }() + } +} + +func (p *BigIntPromise) callFailureFn() { + if p.failureFn != nil { + go func() { + p.failureFn(p.err) + }() + } +}