/*
* @Author: amitshah
* @Date: 2018-04-17 00:55:47
* @Last Modified by: amitshah
* @Last Modified time: 2018-04-28 23:43:00
*/
const messageLib = require('./message');
const channelLib = require('./channel');
const channelStateLib = require('./channelState');
const stateMachineLib = require('./stateMachine/stateMachine');
const util = require('ethereumjs-util');
const events = require('events');
/**
* @class GoNetworks Engine encapsualtes off chain interactions between clients and propogation onto the blockchain.
* The Engine is platform agnostic and adaptble to different blockchains by providing appropriate blockchainService adapters
* Overriding the send callback also allows for custom transport layers and methods (not neccessarily IP network based)
* @extends events.EventEmitter
* @property {BN} msgID=0
* @property {BN} currentBlock=0 - the current block which is synchronized to the onchain mined block value via blockchainService handler callbacks
* @property {Buffer} publicKey - Future Use: ElGamal Elliptic Curve Asymmetric Encryption public key to be sent to channel partners
* @property {InitiatorFactory} initiatorStateMachine=stateMachine.IntiatorFactory - creates a new state machine for mediated transfers you initiate
* @property {TargetFactory} targeStateMachine=stateMachine.TargetFactory - creates a new state machine for mediated transfers that are intended for you
* @property {object} messageState={} - tracks the state of mediated transfer messages using msgID as the key i.e. this.messageState[msgID] = stateMachine.*
* @property {object} channelByPeer={} - channels by peers ethereum address as hex string no 0x prefix
* @property {object} channels={} - channels by on-chain contract address hex string no 0x prefix
* @property {object} pendingChannels={} - used to track newChannel requests initiated by the engine
* @property {function} signatureService
* @property {object} blockchain
*/
class Engine extends events.EventEmitter {
/**
* @constructror.
* @param {Buffer} address - your ethereum address; ETH Address is merely the last 20 bytes of the keccak256 hash of the public key given the public private key pair.
* @param {Function} signatureService - the callback that requests the privatekey for signing of messages. This allows the user to store the private key in a secure store or other means
* @param {BlockchainService} blockchainService - a class extending the BlockchainService class to monitor and propogate transactions on chain. Override for different Blockchains
*/
constructor(address,signatureService,blockchainService){
super();
//dictionary of channels[peerAddress] that are pending mining
this.pendingChannels = {};
this.channels = {};
//dictionary of channels[peerState.address.toString('hex')];
this.channelByPeer = {};
//dictionary of messages[msgID] = statemachine.*
this.messageState = {};
this.currentBlock = new util.BN(0);
this.msgID = new util.BN(0);
this.publicKey;
this.address = address;
this.initiatorStateMachine = stateMachineLib.InitiatorFactory();
this.targetStateMachine = stateMachineLib.TargetFactory();
var self = this;
this.initiatorStateMachine.on("*",function(event,state){
self.handleEvent(event,state);
});
this.targetStateMachine.on("*",function(event,state){
self.handleEvent(event,state);
});
this.signature = signatureService;
this.blockchain = blockchainService;
//sanity check
if(!channelLib.SETTLE_TIMEOUT.gt(channelLib.REVEAL_TIMEOUT)){
throw new Error("SETTLE_TIMEOUT must be strictly and much larger then REVEAL_TIMEOUT");
}
this.currentBlock = new util.BN(0);
}
/**
* Handle an incoming message after it has been deserialized
* @param {message.SignedMessage} message
* @returns {message.Ack}
* @throws "Invalid Message: no signature found"
* @throws "Invalid Message: uknown message received"
*/
onMessage(message){
//TODO: all messages must be signed here?
if(!message.isSigned()){
throw new Error("Invalid Message: no signature found");
}
if(message instanceof messageLib.RequestSecret){
this.onRequestSecret(message);
}else if(message instanceof messageLib.RevealSecret){
this.onRevealSecret(message);
}else if(message instanceof messageLib.MediatedTransfer){
this.onMediatedTransfer(message);
}else if(message instanceof messageLib.DirectTransfer){
this.onDirectTransfer(message);
}else if(message instanceof messageLib.SecretToProof){
this.onSecretToProof(message);
}else{
throw new Error("Invalid Message: uknown message received");
}
return new messageLib.Ack({msgID:message.msgID, messageHash:message.getHash(), to:message.from});
}
onRequestSecret(requestSecret){
if(this.messageState.hasOwnProperty(requestSecret.msgID)){
this.messageState[requestSecret.msgID].applyMessage('receiveRequestSecret',requestSecret);
}
}
onRevealSecret(revealSecret){
//handle reveal secret for all channels that have a lock created by it
//we dont care where it came from unless we want to progress our state machine
var errors = [];
Object.values(this.channelByPeer).map(function (channel) {
try{
channel.handleRevealSecret(revealSecret);
}catch(err){
errors.push(err)
}
});
//update all state machines that are in awaitRevealSecret state
Object.values(this.messageState).map(function (messageState) {
try{
//the state machines will take care of echoing RevealSecrets
//to channel peerStates
messageState.applyMessage('receiveRevealSecret',revealSecret);
}catch(err){
errors.push(err)
}
});
errors.map(function (error) {
console.log(error);
});
}
onSecretToProof(secretToProof){
//handle reveal secret for all channels that have a lock created by it.
//this is in the case where for some reason we get a SecretToProof before
//a reveal secret
//encapsulate in message.RevealSecret type of message, we dont have to sign it
//it is not required
var tempRevealSecret = new messageLib.RevealSecret({secret:secretToProof.secret})
this.signature(tempRevealSecret);
Object.values(this.channelByPeer).map(function (channel) {
try{
channel.handleRevealSecret(tempRevealSecret);
}catch(err){
console.log(err);
}
});
Object.values(this.messageState).map(function (messageState) {
try{
//the state machines will take care of echoing RevealSecrets
//to channel peerStates
messageState.applyMessage('receiveRevealSecret',tempRevealSecret);
}catch(err){
console.log(err);
}
});
if(!this.channelByPeer.hasOwnProperty(secretToProof.from.toString('hex'))){
throw new Error("Invalid SecretToProof: unknown sender");
}
var channel = this.channelByPeer[secretToProof.from.toString('hex')];
channel.handleTransfer(secretToProof,this.currentBlock);
if(this.messageState.hasOwnProperty(secretToProof.msgID)){
this.messageState[secretToProof.msgID].applyMessage('receiveSecretToProof',secretToProof);
}else{
//Something went wrong with the statemachine :(
}
}
onDirectTransfer(directTransfer){
if(!this.channelByPeer.hasOwnProperty(directTransfer.from.toString('hex'))){
throw new Error('Invalid DirectTransfer: channel does not exist');
}
var channel = this.channelByPeer[directTransfer.from.toString('hex')];
if(!channel.isOpen()){
throw new Error('Invalid Channel State:state channel is not open');
}
console.log("EMIT TO UI: transferred:"+directTransfer.transferredAmount.sub(channel.peerState.transferredAmount));
channel.handleTransfer(directTransfer,this.currentBlock);
}
onMediatedTransfer(mediatedTransfer){
if(!this.channelByPeer.hasOwnProperty(mediatedTransfer.from.toString('hex'))){
throw new Error('Invalid MediatedTransfer: channel does not exist');
}
var channel = this.channelByPeer[mediatedTransfer.from.toString('hex')];
if(!channel.isOpen()){
throw new Error('Invalid MediatedTransfer Received:state channel is not open');
}
//register the mediated transfer
channel.handleTransfer(mediatedTransfer,this.currentBlock);
if(mediatedTransfer.target.compare(this.address)===0){
console.log("Start targetStateMachine");
this.messageState[mediatedTransfer.msgID] = new stateMachineLib.MessageState(mediatedTransfer,this.targetStateMachine);
this.messageState[mediatedTransfer.msgID].applyMessage('init',this.currentBlock);
}
}
/**
* Send a locked transfer to your channel partner. This method intiatlizes a initator state machine which will queue the message for send via handleEvent
* @param {Buffer} to - eth address who this message will be sent to. Only differs from target if mediating a transfer
* @param {Buffer} target - eth address of the target.
* @param {BN} amount - amount to lock and send.
* @param {BN} expiration - the absolute block number this locked transfer expires at.
* @param {Buffer} secret - Bytes32 cryptographic secret
* @param {Buffer} hashLock - Bytes32 keccak256(secret) value.
* @throws "Invalid MediatedTransfer: channel does not exist"
* @throws 'Invalid Channel State:state channel is not open'
*/
sendMediatedTransfer(to,target,amount,expiration,secret,hashLock){
if(!this.channelByPeer.hasOwnProperty(to.toString('hex'))){
throw new Error("Invalid MediatedTransfer: channel does not exist");
}
var channel = this.channelByPeer[to.toString('hex')];
if(!channel.isOpen()){
throw new Error('Invalid Channel State:state channel is not open');
}
//var expiration = this.currentBlock.add(channel.SETTLE_TIMEOUT);
var msgID = this.incrementedMsgID();
var mediatedTransferState = ({msgID:msgID,
"lock":{
hashLock:hashLock,
amount:amount,
expiration:expiration,
},
target:to,
initiator:this.address,
currentBlock:this.currentBlock,
secret:secret,
to:channel.peerState.address});
this.messageState[msgID] = new stateMachineLib.MessageState(mediatedTransferState,this.initiatorStateMachine);
this.messageState[msgID].applyMessage('init');
}
/**
* Send a direct transfer to your channel partner. This method calls send(directTransfer) and applies the directTransfer to the local channel state.
* @param {Buffer} to - eth address who this message will be sent to. Only differs from target if mediating a transfer
* @param {Buffer} transferredAmount - the monotonically increasing amount to send. This value is set by taking the previous transferredAmount + amount you want to transfer.
* @throws "Invalid MediatedTransfer: unknown to address"
* @throws 'Invalid DirectTransfer:state channel is not open'
*/
sendDirectTransfer(to,transferredAmount){
if(!this.channelByPeer.hasOwnProperty(to.toString('hex'))){
throw new Error("Invalid MediatedTransfer: unknown to address");
}
var channel = this.channelByPeer[to.toString('hex')];
if(!channel.isOpen()){
throw new Error('Invalid DirectTransfer:state channel is not open');
}
var msgID = this.incrementedMsgID();
var directTransfer = channel.createDirectTransfer(msgID,transferredAmount);
this.signature(directTransfer);
this.send(directTransfer);
channel.handleTransfer(directTransfer);
}
incrementedMsgID(){
this.msgID = this.msgID.add(new util.BN(1));
return this.msgID;
}
/**Send the message. Override this function to define different transport channels
* e.g integrate this with TELEGRAMS Api and securely transfer funds between users on telegram.
* Generate qrcodes for revelSecret message
* or implement webRTC p2p protocol for transport etc.
* @param {message} msg - A message implementation in the message namespace
*/
send(msg){
console.log("SENDING:"+messageLib.SERIALIZE(msg));
}
/** Internal event handlers triggered by state-machine workflows and blockchain events
* @param {string} event - the GOT.* namespaced event triggered asynchronously by external engine components i.e. stateMachine, on-chain event handlers,etc.
* @param {object} state - the accompanying object state
*/
handleEvent(event, state){
try{
if(event.startsWith('GOT.')){
switch(event){
case 'GOT.sendMediatedTransfer':
var channel = this.channelByPeer[state.to.toString('hex')];
if(!channel.isOpen()){
throw new Error("Channel is not open");
}
//msgID,hashLock,amount,expiration,target,initiator,currentBlock
var mediatedTransfer = channel.createMediatedTransfer(state.msgID,
state.lock.hashLock,
state.lock.amount,
state.lock.expiration,
state.target,
state.initiator,
state.currentBlock);
this.signature(mediatedTransfer);
this.send(mediatedTransfer);
channel.handleTransfer(mediatedTransfer);
break;
case 'GOT.sendRequestSecret':
var channel = this.channelByPeer[state.to.toString('hex')];
var requestSecret = new messageLib.RequestSecret({msgID:state.msgID,to:state.from,
hashLock:state.lock.hashLock,amount:state.lock.amount});
this.signature(requestSecret);
this.send(requestSecret);
break;
case 'GOT.sendRevealSecret':
var channel = this.channelByPeer[state.to.toString('hex')];
//technically, this workflow only works when target == to. In mediated transfers
//we need to act more generally and have the state machine tell us where we should
//send this secret (backwards and forwards maybe)
var revealSecret = new messageLib.RevealSecret({to:state.revealTo, secret:state.secret});
this.signature(revealSecret);
this.send(revealSecret);
//we dont register the secret, we wait for the echo Reveal
break;
case 'GOT.sendSecretToProof':
var channel = this.channelByPeer[state.to.toString('hex')];
//OPTIMIZE:technically we can still send sec2proof,
//it would beneficial to our partner saving $$ for lock withdrawal
//but for now we act in no interest of the peer endpoint :( meanie
if(!channel.isOpen()){
throw new Error("Channel is not open");
}
var secretToProof = channel.createSecretToProof(state.msgID,state.secret);
this.signature(secretToProof)
channel.handleTransfer(secretToProof);
this.send(secretToProof);
//TODO: in the future, wait to apply secret to proof locally. We basically locked the state up now
//It makes sense, in a sense. With this implementation, when a lock secret is revealed and echoed back
// the peer MUST accept a valid SecretToProof or no more new transfers can take place as the states are unsynced
//By having the peer echo back, we dont really do much difference, the state is simplex
break;
case 'GOT.closeChannel':
var channel = this.channelByPeer[state.from.toString('hex')];
//TODO emit closing
return this.closeChannel(channel.channelAddress);
//channel.handleClose(this.currentBlock);
break;
case 'GOT.issueSettle':
//TODO emit "IssueSettle to ui + channel";
var channelAddress = state;
console.log("CAN ISSUE SETTLE:"+channelAddress.toString('hex'));
break;
}
return;
}
}
catch(err){
this.handleError(err);
}
}
/*** Internal error handler triggered by errors encountered by handleEvent
* @param {Error} err - the error caught during handleEvent execution
*/
handleError(err){
console.error(err);
}
/*** Blockchain callback when a new block is mined and blockNumber increases.
*This informs the engine of time progression via block number increments crucial for lockedtransfer
* and channel lifecycle management
* @param {BN} block - the latest mined block
*/
onBlock(block){
if(block.lt(this.currentBlock)){
throw new Error("Block Error: block count must be monotonically increasing");
}
this.currentBlock = block;
//handleBlock by all the in-flight messages
//timeout or take action as needed
var self = this;
Object.values(this.messageState).map(function (messageState) {
try{
console.debug("CALL HANDLE BLOCK ON MESSAGE");
messageState.applyMessage('handleBlock',self.currentBlock);
}catch(err){
console.log(err);
}
});
//handleBlock for each of the channels, perhaps SETTLE_TIMEOUT has passed
Object.values(this.channels).map(function(channel){
console.debug("CALL HANDLE BLOCK ON CHANNEL");
var events = channel.onBlock(self.currentBlock);
for(var i=0; i < events.length; i++){
self.handleEvent.apply(events[i]);
}
});
}
/** Create a new channel given the peer ethereum address
* @param {Buffer} peerAddress - eth address
* @returns {Promise} - the promise is settled when the channel is mined. If there is an error during any point of execution in the mining
* the onChannelNewError(peerAddress) is called
*/
newChannel(peerAddress){
//is this a blocking call?
if(!this.pendingChannels.hasOwnProperty(peerAddress.toString('hex')) &&
this.channelByPeer.hasOwnProperty(peerAddress.toString('hex'))
&& this.channelByPeer[peerAddress.toString('hex')].state !== channelLib.CHANNEL_STATE_SETTLED){
throw new Error("Invalid Channel: cannot create new channel as channel already exists with peer and is unsettled");
}
this.pendingChannels[peerAddress.toString('hex')] = true;
var self = this;
var _peerAddress = peerAddress;
return this.blockchain.newChannel(peerAddress,channelLib.SETTLE_TIMEOUT).then(function(vals) {
// ChannelNew(address netting_channel,address participant1,address participant2,uint settle_timeout);
// var channelAddress = vals[0];
// var addressOne = vals[1];
// var addressTwo = vals[2];
// var timeout = vals[3];
//self.onChannelNew(channelAddress,addressOne,addressTwo,timeout);
}).catch(function (err) {
self.onChannelNewError(_peerAddress);
});
};
/** close a channel given the peer ethereum address. The close proof in the state is transferred during the call to close.
* @param {Buffer} channelAddress - the on-chain nettingchannel address of the channel
* @returns {Promise} - the promise is settled when the channel close request is mined. If there is an error during any point of execution in the mining
* the onChannelCloseError(channelAddress) is called
*/
closeChannel(channelAddress){
if(!this.channels.hasOwnProperty(channelAddress.toString('hex'))){
throw new Error("Invalid Close: unknown channel");
}
var channel = this.channels[channelAddress.toString('hex')];
if(!channel.isOpen()){
throw new Error("Invalid Close: Cannot reissue Closed");
}
var proof = channel.issueClose(this.currentBlock);
var self = this;
var _channelAddress = channelAddress;
return this.blockchain.closeChannel(channelAddress,proof).then(function(closingAddress){
//channelAddress,closingAddress,block
//TODO: @Artur, only call this after the transaction is mined i.e. txMulitplexer
// return self.onChannelClose(_channelAddress,closingAddress);
}).catch(function(error){
return self.onChannelCloseError(_channelAddress);
});
}
/** Update the proof after you learn a channel has been closed by the channel counter party
* @param {Buffer} channelAddress - the on-chain nettingchannel address of the channel
* @returns {Promise} - the promise is settled when the channel close request is mined. If there is an error during any point of execution in the mining
* the onTransferUpdatedError(channelAddress) is called
*/
transferUpdate(channelAddress){
if(!this.channels.hasOwnProperty(channelAddress.toString('hex'))){
throw new Error("Invalid TransferUpdate: unknown channel");
}
var channel = this.channels[channelAddress.toString('hex')];
if(channel.isOpen()){
throw new Error("Invalid TransferUpdate: Cannot issue update on open channel");
}
var proof = channel.issueTransferUpdate(this.currentBlock);
var self = this;
var _channelAddress = channelAddress;
return this.blockchain.updateTransfer(channelAddress, proof).then(function(nodeAddress){
//self.onTransferUpdated(nodeAddress)
}).catch(function(err) {
self.onTransferUpdatedError(_channelAddress);
})
}
/** Issue withdraw proofs on-chain for locks that have had their corresponding secret revealed. Locks can be settled on chain once a proof has been sent on-chain.
* Locks can only be withdrawn once.
* @param {Buffer} channelAddress - the on-chain nettingchannel address of the channel
* @returns {Promise} - the promise is settled when the channel close request is mined. If there is an error during any point of execution in the mining
* the onChannelSecretRevealedError(channelAddress) is called for each lock that was not successfully withdrawn on-chain and must be reissued
*/
withdrawPeerOpenLocks(channelAddress){
if(!this.channels.hasOwnProperty(channelAddress.toString('hex'))){
throw new Error("Invalid Withdraw: unknown channel");
}
var channel = this.channels[channelAddress.toString('hex')];
if(channel.isOpen()){
throw new Error("Invalid Withdraw: Cannot issue withdraw on open channel");
}
var openLockProofs = channel.issueWithdrawPeerOpenLocks(this.currentBlock);
var withdraws = [];
for(var i=0; i< openLockProofs.length; i++){
var p = openLockProofs[i];
//nonce,gasPrice,nettingChannelAddress, encodedOpenLock, merkleProof,secret)
var _secret = p.openLock.secret;
var _channelAddress = channelAddress;
var self = this;
var promise = this.blockchain.withdrawLock(channelAddress,p.encodeLock(),p.merkleProof,_secret)
.then(function(vals){
// var secret = vals[0];
// var receiverAddress = vals[1];
// //channelAddress, secret, receiverAddress,block
// return self.onChannelSecretRevealed(_channelAddress,secret,receiverAddress)
})
.catch(function(err){
return self.onChannelSecretRevealedError(_channelAddress,_secret);
})
withdraws.push(promise);
}
return Promise.all(withdraws);
}
/** Settle the channel on-chain after settle_timeout time has passed since closing, unlocking the on-chain collateral and distributing the funds
* according to the proofs and lock withdrawals on chain.
* @param {Buffer} channelAddress - the on-chain nettingchannel address of the channel
* @returns {Promise} - the promise is settled when the channel close request is mined. If there is an error during any point of execution in the mining
* the onChannelSettledError(channelAddress) is called
*/
settleChannel(channelAddress){
if(!this.channels.hasOwnProperty(channelAddress)){
throw new Error("Invalid Settle: unknown channel");
}
var channel = this.channels[channelAddress.toString('hex')];
if(channel.isOpen()){
throw new Error("Invalid Settle: cannot issue settle on open channel");
}
var _channelAddress = channelAddress;
var self = this;
channel.issueSettle(this.currentBlock);
var _channelAddress = channelAddress;
return self.blockChain.settle(_channelAddress).then(function () {
//return self.onChannelSettled(_channelAddress);
}).catch(function(err){
return self.onChannelSettledError(_channelAddress);
});
}
/** Deposit an amount of the ERC20 token into the channel on-chain. After the transaction is mined successfully,
* that amount will be available for net transfer in the channel. This is effectively the collateral locked up during the
* channel lifetime and cannot be freed until the channel is closed and settled.
* @param {Buffer} channelAddress - the on-chain nettingchannel address of the channel
* @param {BN} amount - the amount of the ERC20 token to deposit. The maxium amount of the cumulative deposits is determined by the allowance setup for the channel.
* @see Engine.approveChannel
* @returns {Promise} - the promise is settled when the channel close request is mined. If there is an error during any point of execution in the mining
* the onChannelNewBalanceError(channelAddress) is called
*/
depositChannel(channelAddress,amount){
if(!this.channels.hasOwnProperty(channelAddress)){
throw new Error("Invalid Settle: unknown channel");
}
var channel = this.channels[channelAddress.toString('hex')];
if(!channel.isOpen()){
throw new Error("Invalid Deposite: cannot issue settle on open channel");
}
var _channelAddress = channelAddress;
var self = this;
return self.blockChain.depoist(_channelAddress,amount).then(function (vals) {
// event ChannelNewBalance(address token_address, address participant, uint balance);
// var tokenAddress = vals[0];
// var nodeAddress = vals[1];
// var balance = vals[2];
// return self.onChannelNewBalance(_channelAddress,nodeAddress,balance);
}).catch(function(err){
return self.onChannelNewBalanceError(_channelAddress);
});
}
/** approve the channel to take ERC20 deposits. This must be called before a deposit can be made successfully. This utlimately creates and allowance
* for the channel on the ERC20 contract.
* @param {Buffer} channelAddress - the on-chain nettingchannel address of the channel
* @param {BN} amount - the maximum amount of the ERC20 token to allow the channel to transfer when making a deposit.
* @returns {Promise} - the promise is settled when the channel close request is mined. If there is an error during any point of execution in the mining
* the onApprovalError(channelAddress) is called
*/
approveChannel(channelAddress,amount){
if(!this.channels.hasOwnProperty(channelAddress)){
throw new Error("Invalid approve Channel: unknown channel");
}
var channel = this.channels[channelAddress.toString('hex')];
var _channelAddress = channel.channelAddress;
return self.blockChain.approve(self.blockchain.tokenAddress,channel.channelAddress,amount)
.then(function (vals) {
//event Approval(address indexed _owner, address indexed _spender, uint256 _value);
// var owner = vals[0];
// var spender = vals[1];
// var value = vals[2];
// return self.onApproval(owner,spender,value);
}).catch(function(err){
return self.onApprovalError(_channelAddress);
});
}
/** Approve the channelManager to take the flat fee in GOT ERC20 tokens when a channel is created. This only needs to be called once when the engine is initialized
* @param {BN} amount - the maximum allowance of GOT ERC20 tokens to allow the channelManager to transfer.
* @returns {Promise} - the promise is settled when the channel close request is mined. If there is an error during any point of execution in the mining
* the onApprovalError() is called
*/
approveChannelManager(amount){
return self.blockChain.approve(self.blockchain.gotokenAddress,
self.blockchain.chanelManagerAddress,
amount)
.then(function (vals) {
//event Approval(address indexed _owner, address indexed _spender, uint256 _value);
// var owner = vals[0];
// var spender = vals[1];
// var value = vals[2];
// return self.onApproval(owner,spender,value);
}).catch(function(err){
return self.onApprovalError();
});
}
/** Callback when a ERC20 token approves someone for an allowance
* @param {String} owner - ethereum address hexString
* @param {String} spender - ethereum address hexString
* @param {BN} value - the allowance that was set
*/
onApproval(owner,spender,value){
return true;
};
onApprovalError(address){
return true;
}
/** Callback when a new channel is created by the channel manager
* @param {String} channelAddress - ethereum address hexString
* @param {String} addressOne - ethereum address hexString
* @param {String} addressTwo - ethereum address hexString
* @param {BN} settleTimeout- the settle_timeout for the channel
*/
onChannelNew(channelAddress,addressOne,addressTwo,settleTimeout){
var peerAddress = null;
if(addressOne.compare(this.address)===0){
peerAddress = addressTwo;
}else if(addressTwo.compare(this.address)===0){
peerAddress = addressOne;
}else{
//something very wrong
throw new Error("Invalid Channel Event:unknown new channel");
}
var existingChannel = this.channelByPeer[peerAddress.toString('hex')];
if(existingChannel && existingChannel.state !== channelLib.CHANNEL_STATE_SETTLED){
throw new Error("Invalid Channel: cannot add new channel as it already exists");
}
var stateOne = new channelStateLib.ChannelState({
address:this.address
});
var stateTwo = new channelStateLib.ChannelState({
address:peerAddress
});
//constructor(peerState,myState,channelAddress,settleTimeout,revealTimeout,currentBlock){
var channel = new channelLib.Channel(stateTwo,stateOne,channelAddress,
this.currentBlock);
this.channels[channel.channelAddress.toString('hex')] = channel;
this.channelByPeer[channel.peerState.address.toString('hex')] = channel;
if(this.pendingChannels.hasOwnProperty(peerAddress.toString('hex'))){
delete this.pendingChannels[peerAddress.toString('hex')] ;
}
return true;
}
onChannelNewError(peerAddress){
if(this.pendingChannels.hasOwnProperty(peerAddress.toString('hex'))){
delete this.pendingChannels[peerAddress.toString('hex')] ;
}
return;
//TODO: emit UnableToCreate Channel with Peer
}
/** Callback when a channel has tokens deposited into it on-chain
* @param {String} channelAddress - ethereum address hexString
* @param {String} address - the particpants ethereum address in hexString who deposited the funds
* @param {String} balance - the new deposited balance for the participant in the channel
*/
onChannelNewBalance(channelAddress,address,balance){
this.channels[channelAddress.toString('hex')].onChannelNewBalance(address,balance);
return true;
}
onChannelNewBalanceError(){
return false;
}
/** Callback when a channel is closed on chain identifying which of the partners initiated the close
* @param {String} channelAddress - ethereum address hexString
* @param {String} closingAddress - ethereum address hexString
*/
onChannelClose(channelAddress,closingAddress){
var channel = this.channels[channelAddress.toString('hex')];
channel.onChannelClose(closingAddress, this.currentBlock)
if(closingAddress.compare(this.address) !==0){
return this.transferUpdate(channelAddress)
}
return true;
}
onChannelCloseError(channelAddress,proof){
var channel = this.channels[channelAddress.toString('hex')];
return channel.onChannelCloseError();
}
/** Callback when a the counterpary has updated their transfer proof on-chain
* @param {String} channelAddress - ethereum address hexString
* @param {String} nodeAddress - the party who submitted the proof
*/
onTransferUpdated(channelAddress,nodeAddress){
return this.channels[channelAddress.toString('hex')].onTransferUpdated(nodeAddress,this.currentBlock);
}
onTransferUpdatedError(channelAddress){
return this.channels[channelAddress.toString('hex')].onTransferUpdatedError();
}
/** Callback when a channel is settled on-chain
* @param {String} channelAddress - ethereum address hexString
*/
onChannelSettled(channelAddress){
return this.channels[channelAddress.toString('hex')].onChannelSettled(this.currentBlock);
}
onChannelSettledError(channelAddress){
return this.channels[channelAddress.toString('hex')].onChannelSettledError();;
}
/** Callback when a lock has been withdrawn on-chain. If a user was withholding the secret in a mediate transfer,
* the party can now unlock the pending locks in the other channels. This is why it is essential in a mediated transfer setting
* that each hop decrements the expiration by a safe margin such that they may claim a lock off chain in case of byzantine faults
* @param {String} channelAddress - ethereum address hexString
* @param {String} secret - the 32 byte secret in hexString
* @param {String} receiverAddress - ethereum address hexString which unlocked the lock on-chain
*/
onChannelSecretRevealed(channelAddress, secret, receiverAddress){
return this.channels[channelAddress.toString('hex')].onChannelSecretRevealed(secret,receiverAddress,this.currentBlock);
};
onChannelSecretRevealedError(channelAddress, secret){
return this.channels[channelAddress.toString('hex')].onChannelSecretRevealedError(secret);
};
/** Callback when a channel has been closed and the channel lifetime exceeds the refund interval.
* i.e. channel.closedBlock - channel.openedBlock > refundInterval. This is in hopes to incentives longer lived state channels
* by reducing the cost of their deployment for longer periods.
* @param {String} channelAddress - ethereum address hexString
* @param {String} receiverAddress - ethereum address hexString of the party that received the refund
* @param {BN} amount- the amount of GOT refunded
*/
onRefund(channelAddress,receiverAddress,amount){
return true;
}
}
module.exports = {
Engine
}