区块链应用与以太坊的交互

我们要谈的交互

首先要明确一点,以太坊是一个去中心化的平台,他不可能为了某个项目而新增交互接口。

我这里说的交互是指应用是链上合约的交互,更明确的说,是chainlink,arbitrum,cosmos这些链下应用与链上合约的交互。

所以这里我不是想说以下交互方式:

  • 通过钱包交互:将应用打包成一个网页,连接类似与小狐狸 这样的钱包,与链发生交互。
  • 手动构造交易:构建交易tx并使用私钥对交易进行签名,然后直接发送到链给定的接口上。

对于一些简单的调用,通过上面两种方式是可行的,例如我们只是做一些nft的构造,通过钱包是最合适的。但是对于向arbitrum这类

链上的应用,如果要做一个交互式单步证明,在这个过程中我需要监控链上合约抛出的event,分析event并构造出相应结果。

这个时候钱包就很难插手,而如果主动构造交易并签名,那么过程太繁琐。实际上以太坊上已经提供了相关的工具链。

我们要谈的交互方式就是,通过以太坊支持的工具链实现与以太坊的交互

工具链

简单而言是使用以太坊工具将sol的合约代码转换成go的类文件,并对调用细节进行封装。

而在应用层(arbitrum,chainlink这一层)可以直接将对应参数传过去就可以.

以arbitrum为例:

生成go代码

生成工具在: https://github.com/OffchainLabs/nitro/blob/master/solgen/gen.go

由于arbitrum用到链makefile,所我们没法通过运行这个文件(go run gen.go)的方式,去生成合约文件。

不过实际上这是一个路径的问题,在代码的第71行:

go 复制代码
    filePaths, err := filepath.Glob(filepath.Join(parent, "contracts", "build", "contracts", "src", "*", "*.sol", "*.json"))
	if err != nil {
		log.Fatal(err)
	}

	filePathsSafeSmartAccount, err := filepath.Glob(filepath.Join(parent, "safe-smart-account", "build", "artifacts", "contracts", "*", "*.sol", "*.json"))
	if err != nil {
		log.Fatal(err)
	}
	filePathsSafeSmartAccountOuter, err := filepath.Glob(filepath.Join(parent, "safe-smart-account", "build", "artifacts", "contracts", "*.sol", "*.json"))
	if err != nil {
		log.Fatal(err)
	}

这里实际上就指定了合约代码的路径,当然如果只是初次下载合约文件应该是看不到build目录的,需要在合约所在项目构建一下,才能生成这个build文件

构造方法:

sh 复制代码
yarn --cwd contracts build
yarn --cwd contracts build:forge:yul
# 其实就是hardhat compile的产物

然后就会在solgen这个目录下生成对应的go文件

我们具体看一下这个生成的代码怎么用

使用生成的代码

首先可以看到在生成的代码中,每一个合约都有一个对应的类

eg:

go 复制代码
// ChallengeLibTransactorRaw is an auto generated low-level write-only Go binding around an Ethereum contract.
type ChallengeLibTransactorRaw struct {
	Contract *ChallengeLibTransactor // Generic write-only contract binding to access the raw methods on
}

合约中的方法则对应到类的方法

eg:

go 复制代码
// Solidity: function oneStepProveExecution(uint64 challengeIndex, (uint256,uint256,bytes32[],uint256) selection, bytes proof) returns()
func (_ChallengeManager *ChallengeManagerTransactor) OneStepProveExecution(opts *bind.TransactOpts, challengeIndex uint64, selection ChallengeLibSegmentSelection, proof []byte) (*types.Transaction, error) {
	return _ChallengeManager.contract.Transact(opts, "oneStepProveExecution", challengeIndex, selection, proof)
}

其中关键在与这里的opts,如果我们继续进到这个Transact方法里面会发现,链上的信息都是由这里的opts获取的,用户签名接口,用户信息等

那么对于一个合约调用就分为两部分,一是调用参数,也就是这里的opts和合约参数,一是对接一台的的client

参数

进入到这里的opts,这是以太坊里面的数据结构

go 复制代码
// valid Ethereum transaction.
type TransactOpts struct {
	From   common.Address // Ethereum account to send the transaction from
	Nonce  *big.Int       // Nonce to use for the transaction execution (nil = use pending state)
	Signer SignerFn       // Method to use for signing the transaction (mandatory)

	Value     *big.Int // Funds to transfer along the transaction (nil = 0 = no funds)
	GasPrice  *big.Int // Gas price to use for the transaction execution (nil = gas price oracle)
	GasFeeCap *big.Int // Gas fee cap to use for the 1559 transaction execution (nil = gas price oracle)
	GasTipCap *big.Int // Gas priority fee cap to use for the 1559 transaction execution (nil = gas price oracle)
	GasLimit  uint64   // Gas limit to set for the transaction execution (0 = estimate)
	GasMargin uint64   // Arbitrum: adjusts gas estimate by this many basis points (0 = no adjustment)

	Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)

	NoSend bool // Do all transact steps but do not send the transaction
}

我们可以看到,这里包含用户信息的签名数据

对于链下的开发者,我们需要构造这个结构,来调用方法

client

我们有链合约的调用参数,那么就需要有一个client来为我们发送交易,(虽然构造交易的时候也用到链client,但这都是已经被工具封装好的,开发者没必要细究它是怎么构建的)

实际上client是在我们构建合约对象时构建的

go 复制代码
// NewChallengeManager creates a new instance of ChallengeManager, bound to a specific deployed contract.
func NewChallengeManager(address common.Address, backend bind.ContractBackend) (*ChallengeManager, error) {
	contract, err := bindChallengeManager(address, backend, backend, backend)
	if err != nil {
		return nil, err
	}
	return &ChallengeManager{ChallengeManagerCaller: ChallengeManagerCaller{contract: contract}, ChallengeManagerTransactor: ChallengeManagerTransactor{contract: contract}, ChallengeManagerFilterer: ChallengeManagerFilterer{contract: contract}}, nil
}

在构建ChallengeManger这个合约对象时,我们需要给他一个backend,这里的backend就是链的client,地址也就是链上的合约地址

可以看到backend也是bind这个包里面的,实际上它也是以太坊源码里面的包

go 复制代码
type ContractBackend interface {
	ContractCaller
	ContractTransactor
	ContractFilterer
}

type ContractCaller interface {
	// CodeAt returns the code of the given account. This is needed to differentiate
	// between contract internal errors and the local chain being out of sync.
	CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error)

	// CallContract executes an Ethereum contract call with the specified data as the
	// input.
	CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
}
type ContractTransactor interface {
	ethereum.GasEstimator
	ethereum.GasPricer
	ethereum.GasPricer1559
	ethereum.TransactionSender

	// HeaderByNumber returns a block header from the current canonical chain. If
	// number is nil, the latest known header is returned.
	HeaderByNumber(ctx context.Context, number *big.Int) (*types.Header, error)

	// PendingCodeAt returns the code of the given account in the pending state.
	PendingCodeAt(ctx context.Context, account common.Address) ([]byte, error)

	// PendingNonceAt retrieves the current pending nonce associated with an account.
	PendingNonceAt(ctx context.Context, account common.Address) (uint64, error)
}

type ContractFilterer interface {
	ethereum.LogFilterer
}

这个client看起来构造很麻烦,实际上也是有迹可循的,这里面都是以太坊里的数据结构,所以理论上以太坊里面已经有对象实现了这些接口

go 复制代码
type Client struct {
	c rpc.ClientInterface
}
type ClientInterface interface {
	CallContext(ctx_in context.Context, result interface{}, method string, args ...interface{}) error
	EthSubscribe(ctx context.Context, channel interface{}, args ...interface{}) (*ClientSubscription, error)
	BatchCallContext(ctx context.Context, b []BatchElem) error
	Close()
}

// Client represents a connection to an RPC server.
type Client struct {
	idgen    func() ID // for subscriptions
	isHTTP   bool      // connection type: http, ws or ipc
	services *serviceRegistry

	idCounter atomic.Uint32

	// This function, if non-nil, is called when the connection is lost.
	reconnectFunc reconnectFunc

	// config fields
	batchItemLimit       int
	batchResponseMaxSize int

	// writeConn is used for writing to the connection on the caller's goroutine. It should
	// only be accessed outside of dispatch, with the write lock held. The write lock is
	// taken by sending on reqInit and released by sending on reqSent.
	writeConn jsonWriter

	// for dispatch
	close       chan struct{}
	closing     chan struct{}    // closed when client is quitting
	didClose    chan struct{}    // closed when client quits
	reconnected chan ServerCodec // where write/reconnect sends the new connection
	readOp      chan readOp      // read messages
	readErr     chan error       // errors from read
	reqInit     chan *requestOp  // register response IDs, takes write lock
	reqSent     chan error       // signals write completion, releases write lock
	reqTimeout  chan *requestOp  // removes response IDs when call timeout expires
}
相关推荐
天涯学馆7 天前
以太坊基础知识结构详解
web3·以太坊
Daniel_18717 天前
区块链技术与应用-PKU 学习笔记
区块链·以太坊·比特币
花花花12 个月前
如何使用智能合约铸造 NFT —— 以 NftMarket 合约为例
去中心化·区块链·智能合约·以太坊·nft·sepolia
bighu2 个月前
chainLink vrf实验
区块链·l2·chainlink·vrf
bighu2 个月前
以太坊Rollup方案之 arbitrum(1)
区块链·以太坊·arbitrum·l2
Mindfulness code2 个月前
以太坊开发环境
区块链·以太坊
代码与野兽3 个月前
既有比特币,为什么还要有以太坊?聊聊以太坊
web3·区块链·以太坊
Z3r4y4 个月前
【区块链】浅谈面向小白的关于BlockChain那些事
区块链·智能合约·it·以太坊·比特币
百色彭于晏4 个月前
【区块链】JavaScript连接web3钱包,实现测试网络中的 Sepolia ETH余额查询、转账功能
开发语言·javascript·web3·区块链·eth·以太坊