Location>code7788 >text

Blockchain Applications Interacting with Ethernet

Popularity:251 ℃/2024-09-11 18:07:31

The interaction we're talking about

First of all, let's make it clear that Ether is a decentralized platform, and it's not possible for him to add new interactive interfaces for the sake of a particular project.
The interaction I'm talking about here is the interaction between the application and the on-chain contract, or more specifically, the interaction between the off-chain application and the on-chain contract, such as chainlink, arbitrum, cosmos.
So here I'm not trying to talk about the following interactions:

  • Interact via wallet: package the app as a web page that connects similarly with thefig. sly and treacherous person Such a wallet that interacts with the chain.
  • Manual construction of the transaction: construct the transaction tx and sign it with the private key, then send it directly to the interface given by the chain.

For some simple calls, it is feasible to go through the above two ways, e.g. if we are just doing some nft construction, going through the wallet is the way to go. But for calls to arbitrum, such as
If I want to do an interactive single-step proof for an on-chain application, I need to monitor the event thrown by the on-chain contract, analyze the event, and construct the corresponding result during the process.
At this point it becomes difficult for the wallet to step in, and the process is too cumbersome if the transaction is actively constructed and signed. The toolchain for this is actually already available on Ether.

The type of interaction we're going to talk about is that of interacting with the Ethernet via an Ethernet-backed toolchain implementation

toolchain

工具链

In simple terms it is the use of an ethereum tool to convert sol's contract code into a go class file and encapsulate the call details.
In the application layer (arbitrum, chainlink) you can pass the corresponding parameters directly.

Take arbitrum for example.

Generate go code

Generate tools in:/OffchainLabs/nitro/blob/master/solgen/
Since arbitrum use chain makefile, so we can not run this file (go run) way, to generate the contract file.
It's actually a path issue though, on line 71 of the code:

    filePaths, err := ((parent, "contracts", "build", "contracts", "src", "*", "*.sol", "*.json"))
	if err != nil {
		(err)
	}

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

This actually specifies the path to the contract code, of course, if you just download the contract file for the first time, you should not see the build directory, and you need to build the project where the contract is located in order to generate the build file.

Construction Methods:

yarn --cwd contracts build
yarn --cwd contracts build:forge:yul
# In fact, it'shardhat compileproduct of

Then the corresponding go file will be generated in the solgen directory
solgen

Let's look specifically at how this generated code works

Using the generated code

First you can see that in the generated code, each contract has a corresponding class
eg:

// 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
}

The methods in the contract then correspond to the methods of the class

eg:

// Solidity: function oneStepProveExecution(uint64 challengeIndex, (uint256,uint256,bytes32[],uint256) selection, bytes proof) returns()
func (_ChallengeManager *ChallengeManagerTransactor) OneStepProveExecution(opts *, challengeIndex uint64, selection ChallengeLibSegmentSelection, proof []byte) (*, error) {
	return _ChallengeManager.(opts, "oneStepProveExecution", challengeIndex, selection, proof)
}

The key here is with the opts, if we continue into the Transact method will find that the chain of information are obtained by the opts here, the user signature interface, user information and so on.

Then for a contract call is divided into two parts, one is the call parameters, that is, the opts and contract parameters here, and the other is the client of the docking machine.

parameters

Go to the opts here, this is the data structure inside the ethereum.

// valid Ethereum transaction.
type TransactOpts struct {
	From    // Ethereum account to send the transaction from
	Nonce  *       // Nonce to use for the transaction execution (nil = use pending state)
	Signer SignerFn       // Method to use for signing the transaction (mandatory)

	Value     * // Funds to transfer along the transaction (nil = 0 = no funds)
	GasPrice  * // Gas price to use for the transaction execution (nil = gas price oracle)
	GasFeeCap * // Gas fee cap to use for the 1559 transaction execution (nil = gas price oracle)
	GasTipCap * // 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  // Network context to support cancellation and timeouts (nil = no timeout)

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

As we can see, here is the signature data containing the user's information

For developers down the chain, we need to construct this structure to call the method

client

We have the call parameters for the chain contract, so we need a client to send the transaction for us. (Although the chain client is also used in constructing the transaction, this is already encapsulated by the tool, so there is no need for the developer to look into how it is constructed.)

The client is actually constructed when we build the contract object

// NewChallengeManager creates a new instance of ChallengeManager, bound to a specific deployed contract.
func NewChallengeManager(address , backend ) (*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
}

In the construction of ChallengeManger contract object, we need to give him a backend, here the backend is the chain of clients, the address is the chain of contracts address

You can see that backend is also in the bind package, which is actually a package in the ethereum source code.

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 , contract , blockNumber *) ([]byte, error)

	// CallContract executes an Ethereum contract call with the specified data as the
	// input.
	CallContract(ctx , call , blockNumber *) ([]byte, error)
}
type ContractTransactor interface {
	
	
	ethereum.GasPricer1559
	

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

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

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

type ContractFilterer interface {
	
}

The client looks like a pain in the ass to construct, but it's actually well documented. It's all about the data structures in the ether, so theoretically there are already objects in the ether that implement these interfaces

type Client struct {
	c 
}
type ClientInterface interface {
	CallContext(ctx_in , result interface{}, method string, args ...interface{}) error
	EthSubscribe(ctx , channel interface{}, args ...interface{}) (*ClientSubscription, error)
	BatchCallContext(ctx , 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
}